Coverage Report

Created: 2026-06-25 06:46

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/mpv/audio/out/ao_pcm.c
Line
Count
Source
1
/*
2
 * PCM audio output driver
3
 *
4
 * Original author: Atmosfear
5
 *
6
 * This file is part of mpv.
7
 *
8
 * mpv is free software; you can redistribute it and/or
9
 * modify it under the terms of the GNU Lesser General Public
10
 * License as published by the Free Software Foundation; either
11
 * version 2.1 of the License, or (at your option) any later version.
12
 *
13
 * mpv is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
 * GNU Lesser General Public License for more details.
17
 *
18
 * You should have received a copy of the GNU Lesser General Public
19
 * License along with mpv.  If not, see <http://www.gnu.org/licenses/>.
20
 */
21
22
#include <stdio.h>
23
#include <stdlib.h>
24
#include <string.h>
25
26
#include <libavutil/common.h>
27
28
#include "mpv_talloc.h"
29
30
#include "options/m_option.h"
31
#include "options/path.h"
32
#include "audio/format.h"
33
#include "ao.h"
34
#include "internal.h"
35
#include "common/msg.h"
36
#include "osdep/endian.h"
37
#include "osdep/io.h"
38
39
#ifdef _WIN32
40
// for GetFileType to detect pipes
41
#include <windows.h>
42
#include <io.h>
43
#endif
44
45
struct priv {
46
    char *outputfilename;
47
    bool waveheader;
48
    bool append;
49
    uint64_t data_length;
50
    FILE *fp;
51
};
52
53
2
#define WAV_ID_RIFF 0x46464952 /* "RIFF" */
54
2
#define WAV_ID_WAVE 0x45564157 /* "WAVE" */
55
2
#define WAV_ID_FMT  0x20746d66 /* "fmt " */
56
2
#define WAV_ID_DATA 0x61746164 /* "data" */
57
4
#define WAV_ID_PCM  0x0001
58
0
#define WAV_ID_FLOAT_PCM  0x0003
59
2
#define WAV_ID_FORMAT_EXTENSIBLE 0xfffe
60
61
static void fput16le(uint16_t val, FILE *fp)
62
12
{
63
12
    uint8_t bytes[2] = {val, val >> 8};
64
12
    fwrite(bytes, 1, 2, fp);
65
12
}
66
67
static void fput32le(uint32_t val, FILE *fp)
68
28
{
69
28
    uint8_t bytes[4] = {val, val >> 8, val >> 16, val >> 24};
70
28
    fwrite(bytes, 1, 4, fp);
71
28
}
72
73
static void write_wave_header(struct ao *ao, FILE *fp, uint64_t data_length)
74
2
{
75
2
    uint16_t fmt = ao->format == AF_FORMAT_FLOAT ? WAV_ID_FLOAT_PCM : WAV_ID_PCM;
76
2
    int bits = af_fmt_to_bytes(ao->format) * 8;
77
78
    // Master RIFF chunk
79
2
    fput32le(WAV_ID_RIFF, fp);
80
    // RIFF chunk size: 'WAVE' + 'fmt ' + 4 + 40 +
81
    // data chunk hdr (8) + data length
82
2
    fput32le(12 + 40 + 8 + data_length, fp);
83
2
    fput32le(WAV_ID_WAVE, fp);
84
85
    // Format chunk
86
2
    fput32le(WAV_ID_FMT, fp);
87
2
    fput32le(40, fp);
88
2
    fput16le(WAV_ID_FORMAT_EXTENSIBLE, fp);
89
2
    fput16le(ao->channels.num, fp);
90
2
    fput32le(ao->samplerate, fp);
91
2
    fput32le(MPCLAMP(ao->bps, 0, UINT32_MAX), fp);
92
2
    fput16le(ao->channels.num * (bits / 8), fp);
93
2
    fput16le(bits, fp);
94
95
    // Extension chunk
96
2
    fput16le(22, fp);
97
2
    fput16le(bits, fp);
98
2
    fput32le(mp_chmap_to_waveext(&ao->channels), fp);
99
    // 2 bytes format + 14 bytes guid
100
2
    fput32le(fmt, fp);
101
2
    fput32le(0x00100000, fp);
102
2
    fput32le(0xAA000080, fp);
103
2
    fput32le(0x719B3800, fp);
104
105
    // Data chunk
106
2
    fput32le(WAV_ID_DATA, fp);
107
2
    fput32le(data_length, fp);
108
2
}
109
110
static int init(struct ao *ao)
111
1
{
112
1
    struct priv *priv = ao->priv;
113
114
1
    char *outputfilename = mp_get_user_path(priv, ao->global, priv->outputfilename);
115
1
    if (!outputfilename) {
116
1
        outputfilename = talloc_strdup(priv, priv->waveheader ? "audiodump.wav"
117
1
                                                              : "audiodump.pcm");
118
1
    }
119
120
1
    ao->format = af_fmt_from_planar(ao->format);
121
122
1
    if (priv->waveheader) {
123
        // WAV files must have one of the following formats
124
125
        // And they don't work in big endian; fixing it would be simple, but
126
        // nobody cares.
127
1
        if (BYTE_ORDER == BIG_ENDIAN) {
128
0
            MP_FATAL(ao, "Not supported on big endian.\n");
129
0
            return -1;
130
0
        }
131
132
1
        switch (ao->format) {
133
0
        case AF_FORMAT_U8:
134
1
        case AF_FORMAT_S16:
135
1
        case AF_FORMAT_S32:
136
1
        case AF_FORMAT_FLOAT:
137
1
             break;
138
0
        default:
139
0
            if (!af_fmt_is_spdif(ao->format))
140
0
                ao->format = AF_FORMAT_S16;
141
0
            break;
142
1
        }
143
1
    }
144
145
1
    struct mp_chmap_sel sel = {0};
146
1
    mp_chmap_sel_add_waveext(&sel);
147
1
    if (!ao_chmap_sel_adjust(ao, &sel, &ao->channels))
148
0
        return -1;
149
150
1
    ao->bps = ao->channels.num * (int64_t)ao->samplerate * af_fmt_to_bytes(ao->format);
151
152
1
    MP_INFO(ao, "File: %s (%s)\nPCM: Samplerate: %d Hz Channels: %d Format: %s\n",
153
1
            outputfilename,
154
1
            priv->waveheader ? "WAVE" : "RAW PCM", ao->samplerate,
155
1
            ao->channels.num, af_fmt_to_str(ao->format));
156
157
1
    priv->fp = fopen(outputfilename, priv->append ? "ab" : "wb");
158
1
    if (!priv->fp) {
159
0
        MP_ERR(ao, "Failed to open %s for writing!\n", outputfilename);
160
0
        return -1;
161
0
    }
162
1
    if (priv->waveheader)  // Reserve space for wave header
163
1
        write_wave_header(ao, priv->fp, 0x7ffff000);
164
1
    ao->untimed = true;
165
1
    ao->device_buffer = 1 << 16;
166
167
1
    return 0;
168
1
}
169
170
// close audio device
171
static void uninit(struct ao *ao)
172
1
{
173
1
    struct priv *priv = ao->priv;
174
175
1
    if (priv->waveheader) {    // Rewrite wave header
176
1
        bool broken_seek = false;
177
#ifdef _WIN32
178
        // Windows, in its usual idiocy "emulates" seeks on pipes so it always
179
        // looks like they work. So we have to detect them brute-force.
180
        broken_seek = FILE_TYPE_DISK !=
181
            GetFileType((HANDLE)_get_osfhandle(_fileno(priv->fp)));
182
#endif
183
1
        if (broken_seek || fseek(priv->fp, 0, SEEK_SET) != 0)
184
1
            MP_ERR(ao, "Could not seek to start, WAV size headers not updated!\n");
185
1
        else {
186
1
            if (priv->data_length > 0xfffff000) {
187
0
                MP_ERR(ao, "File larger than allowed for "
188
0
                       "WAV files, may play truncated!\n");
189
0
                priv->data_length = 0xfffff000;
190
0
            }
191
1
            write_wave_header(ao, priv->fp, priv->data_length);
192
1
        }
193
1
    }
194
1
    fclose(priv->fp);
195
1
}
196
197
static bool audio_write(struct ao *ao, void **data, int samples)
198
1
{
199
1
    struct priv *priv = ao->priv;
200
1
    int len = samples * ao->sstride;
201
202
1
    fwrite(data[0], len, 1, priv->fp);
203
1
    priv->data_length += len;
204
205
1
    return true;
206
1
}
207
208
static void get_state(struct ao *ao, struct mp_pcm_state *state)
209
5
{
210
5
    state->free_samples = ao->device_buffer;
211
5
    state->queued_samples = 0;
212
5
    state->delay = 0;
213
5
}
214
215
static bool set_pause(struct ao *ao, bool paused)
216
0
{
217
0
    return true; // signal support so common code doesn't write silence
218
0
}
219
220
static void start(struct ao *ao)
221
1
{
222
    // we use data immediately
223
1
}
224
225
static void reset(struct ao *ao)
226
0
{
227
0
}
228
229
#define OPT_BASE_STRUCT struct priv
230
231
const struct ao_driver audio_out_pcm = {
232
    .description = "RAW PCM/WAVE file writer audio output",
233
    .name      = "pcm",
234
    .init      = init,
235
    .uninit    = uninit,
236
    .get_state = get_state,
237
    .set_pause = set_pause,
238
    .write     = audio_write,
239
    .start     = start,
240
    .reset     = reset,
241
    .priv_size = sizeof(struct priv),
242
    .priv_defaults = &(const struct priv) { .waveheader = true },
243
    .options = (const struct m_option[]) {
244
        {"file", OPT_STRING(outputfilename), .flags = M_OPT_FILE},
245
        {"waveheader", OPT_BOOL(waveheader)},
246
        {"append", OPT_BOOL(append)},
247
        {0}
248
    },
249
    .options_prefix = "ao-pcm",
250
};