/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 | | }; |