summaryrefslogtreecommitdiffstats
path: root/audio/out/ao_pcm.c
blob: 7d2656be492a6773cfff9be9bba0fef888090293 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
/*
 * 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 <http://www.gnu.org/licenses/>.
 */

#include "config.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <libavutil/common.h>

#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 __MINGW32__
// for GetFileType to detect pipes
#include <windows.h>
#include <io.h>
#endif

struct priv {
    char *outputfilename;
    int waveheader;
    int 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(ao->bps, 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 * 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;

    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 __MINGW32__
        // 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 int get_space(struct ao *ao)
{
    return 65536;
}

static int play(struct ao *ao, void **data, int samples, int flags)
{
    struct priv *priv = ao->priv;
    int len = samples * ao->sstride;

    fwrite(data[0], len, 1, priv->fp);
    priv->data_length += len;
    return samples;
}

#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_space = get_space,
    .play      = play,
    .reports_underruns = true, // not a thing
    .priv_size = sizeof(struct priv),
    .priv_defaults = &(const struct priv) { .waveheader = 1 },
    .options = (const struct m_option[]) {
        {"file", OPT_STRING(outputfilename), .flags = M_OPT_FILE},
        {"waveheader", OPT_FLAG(waveheader)},
        {"append", OPT_FLAG(append)},
        {0}
    },
    .options_prefix = "ao-pcm",
};