/* * PCM audio output driver * * Original author: Atmosfear * * This file is part of mpv. * * mpv is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * mpv is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with mpv. If not, see . */ #include #include #include #include #include "mpv_talloc.h" #include "options/m_option.h" #include "audio/format.h" #include "ao.h" #include "internal.h" #include "common/msg.h" #include "osdep/endian.h" #ifdef _WIN32 // for GetFileType to detect pipes #include #include #endif struct priv { char *outputfilename; bool waveheader; bool append; uint64_t data_length; FILE *fp; }; #define WAV_ID_RIFF 0x46464952 /* "RIFF" */ #define WAV_ID_WAVE 0x45564157 /* "WAVE" */ #define WAV_ID_FMT 0x20746d66 /* "fmt " */ #define WAV_ID_DATA 0x61746164 /* "data" */ #define WAV_ID_PCM 0x0001 #define WAV_ID_FLOAT_PCM 0x0003 #define WAV_ID_FORMAT_EXTENSIBLE 0xfffe static void fput16le(uint16_t val, FILE *fp) { uint8_t bytes[2] = {val, val >> 8}; fwrite(bytes, 1, 2, fp); } static void fput32le(uint32_t val, FILE *fp) { uint8_t bytes[4] = {val, val >> 8, val >> 16, val >> 24}; fwrite(bytes, 1, 4, fp); } static void write_wave_header(struct ao *ao, FILE *fp, uint64_t data_length) { uint16_t fmt = ao->format == AF_FORMAT_FLOAT ? WAV_ID_FLOAT_PCM : WAV_ID_PCM; int bits = af_fmt_to_bytes(ao->format) * 8; // Master RIFF chunk fput32le(WAV_ID_RIFF, fp); // RIFF chunk size: 'WAVE' + 'fmt ' + 4 + 40 + // data chunk hdr (8) + data length fput32le(12 + 40 + 8 + data_length, fp); fput32le(WAV_ID_WAVE, fp); // Format chunk fput32le(WAV_ID_FMT, fp); fput32le(40, fp); fput16le(WAV_ID_FORMAT_EXTENSIBLE, fp); fput16le(ao->channels.num, fp); fput32le(ao->samplerate, fp); fput32le(MPCLAMP(ao->bps, 0, UINT32_MAX), fp); fput16le(ao->channels.num * (bits / 8), fp); fput16le(bits, fp); // Extension chunk fput16le(22, fp); fput16le(bits, fp); fput32le(mp_chmap_to_waveext(&ao->channels), fp); // 2 bytes format + 14 bytes guid fput32le(fmt, fp); fput32le(0x00100000, fp); fput32le(0xAA000080, fp); fput32le(0x719B3800, fp); // Data chunk fput32le(WAV_ID_DATA, fp); fput32le(data_length, fp); } static int init(struct ao *ao) { struct priv *priv = ao->priv; char *outputfilename = priv->outputfilename; if (!outputfilename) { outputfilename = talloc_strdup(priv, priv->waveheader ? "audiodump.wav" : "audiodump.pcm"); } ao->format = af_fmt_from_planar(ao->format); if (priv->waveheader) { // WAV files must have one of the following formats // And they don't work in big endian; fixing it would be simple, but // nobody cares. if (BYTE_ORDER == BIG_ENDIAN) { MP_FATAL(ao, "Not supported on big endian.\n"); return -1; } switch (ao->format) { case AF_FORMAT_U8: case AF_FORMAT_S16: case AF_FORMAT_S32: case AF_FORMAT_FLOAT: break; default: if (!af_fmt_is_spdif(ao->format)) ao->format = AF_FORMAT_S16; break; } } struct mp_chmap_sel sel = {0}; mp_chmap_sel_add_waveext(&sel); if (!ao_chmap_sel_adjust(ao, &sel, &ao->channels)) return -1; ao->bps = ao->channels.num * (int64_t)ao->samplerate * af_fmt_to_bytes(ao->format); MP_INFO(ao, "File: %s (%s)\nPCM: Samplerate: %d Hz Channels: %d Format: %s\n", outputfilename, priv->waveheader ? "WAVE" : "RAW PCM", ao->samplerate, ao->channels.num, af_fmt_to_str(ao->format)); priv->fp = fopen(outputfilename, priv->append ? "ab" : "wb"); if (!priv->fp) { MP_ERR(ao, "Failed to open %s for writing!\n", outputfilename); return -1; } if (priv->waveheader) // Reserve space for wave header write_wave_header(ao, priv->fp, 0x7ffff000); ao->untimed = true; ao->device_buffer = 1 << 16; return 0; } // close audio device static void uninit(struct ao *ao) { struct priv *priv = ao->priv; if (priv->waveheader) { // Rewrite wave header bool broken_seek = false; #ifdef _WIN32 // Windows, in its usual idiocy "emulates" seeks on pipes so it always // looks like they work. So we have to detect them brute-force. broken_seek = FILE_TYPE_DISK != GetFileType((HANDLE)_get_osfhandle(_fileno(priv->fp))); #endif if (broken_seek || fseek(priv->fp, 0, SEEK_SET) != 0) MP_ERR(ao, "Could not seek to start, WAV size headers not updated!\n"); else { if (priv->data_length > 0xfffff000) { MP_ERR(ao, "File larger than allowed for " "WAV files, may play truncated!\n"); priv->data_length = 0xfffff000; } write_wave_header(ao, priv->fp, priv->data_length); } } fclose(priv->fp); } static bool audio_write(struct ao *ao, void **data, int samples) { struct priv *priv = ao->priv; int len = samples * ao->sstride; fwrite(data[0], len, 1, priv->fp); priv->data_length += len; return true; } static void get_state(struct ao *ao, struct mp_pcm_state *state) { state->free_samples = ao->device_buffer; state->queued_samples = 0; state->delay = 0; } static bool set_pause(struct ao *ao, bool paused) { return true; // signal support so common code doesn't write silence } static void start(struct ao *ao) { // we use data immediately } static void reset(struct ao *ao) { } #define OPT_BASE_STRUCT struct priv const struct ao_driver audio_out_pcm = { .description = "RAW PCM/WAVE file writer audio output", .name = "pcm", .init = init, .uninit = uninit, .get_state = get_state, .set_pause = set_pause, .write = audio_write, .start = start, .reset = reset, .priv_size = sizeof(struct priv), .priv_defaults = &(const struct priv) { .waveheader = true }, .options = (const struct m_option[]) { {"file", OPT_STRING(outputfilename), .flags = M_OPT_FILE}, {"waveheader", OPT_BOOL(waveheader)}, {"append", OPT_BOOL(append)}, {0} }, .options_prefix = "ao-pcm", };