ao_pcm, core: use new API in ao_pcm, change timing with it

Change ao_pcm to use the new audio output driver API and clean up some
of the code. Rewrite the logic controlling how playback timing works
when using -ao pcm. Deprecate the "fast" suboption; its only effect
now is to print a warning, but it's still accepted so that specifying
it is not an error.

Before, timing with -ao pcm and video enabled had two possible
modes. In the default mode playback speed was rather arbitrary - not
realtime, but not particularly fast. -ao pcm:fast tried to play back
at maximum video playback speed - mostly succeeding, but not quite
guaranteed to work in all cases. Now the default is to play at
realtime speed. The -benchmark option can now be used to get faster
playback (same as the video-only case). In the audio-only case
playback is always maximum speed.
This commit is contained in:
Uoti Urpala 2011-05-05 21:34:17 +03:00
parent 2fae42d00e
commit 40f6ab5064
5 changed files with 129 additions and 142 deletions

View File

@ -3020,11 +3020,6 @@ When not included, raw PCM will be generated.
Write the sound to <filename> instead of the default
audiodump.wav.
If nowaveheader is specified, the default is audiodump.pcm.
.IPs "fast\ "
Try to dump faster than realtime.
Make sure the output does not get truncated (usually with
"Too many video packets in buffer" message).
It is normal that you get a "Your system is too SLOW to play this!" message.
.RE
.PD 1
.

View File

@ -24,13 +24,14 @@
#include <stdlib.h>
#include <string.h>
#include "libavutil/common.h"
#include "mpbswap.h"
#include <libavutil/common.h>
#include "talloc.h"
#include "subopt-helper.h"
#include "libaf/af_format.h"
#include "libaf/reorder_ch.h"
#include "audio_out.h"
#include "audio_out_internal.h"
#include "mp_msg.h"
#ifdef __MINGW32__
@ -38,22 +39,13 @@
#include <windows.h>
#endif
static const ao_info_t info =
{
"RAW PCM/WAVE file writer audio output",
"pcm",
"Atmosfear",
""
struct priv {
char *outputfilename;
int waveheader;
uint64_t data_length;
FILE *fp;
};
LIBAO_EXTERN(pcm)
extern int vo_pts;
static char *ao_outputfilename = NULL;
static int ao_pcm_waveheader = 1;
static int fast = 0;
#define WAV_ID_RIFF 0x46464952 /* "RIFF" */
#define WAV_ID_WAVE 0x45564157 /* "WAVE" */
#define WAV_ID_FMT 0x20746d66 /* "fmt " */
@ -62,30 +54,30 @@ static int fast = 0;
#define WAV_ID_FLOAT_PCM 0x0003
#define WAV_ID_FORMAT_EXTENSIBLE 0xfffe
/* init with default values */
static uint64_t data_length;
static FILE *fp = NULL;
static void fput16le(uint16_t val, FILE *fp) {
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) {
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(FILE *fp, uint64_t data_length) {
int use_waveex = (ao_data.channels >= 5 && ao_data.channels <= 8);
uint16_t fmt = (ao_data.format == AF_FORMAT_FLOAT_LE) ? WAV_ID_FLOAT_PCM : WAV_ID_PCM;
static void write_wave_header(struct ao *ao, FILE *fp, uint64_t data_length)
{
bool use_waveex = ao->channels >= 5 && ao->channels <= 8;
uint16_t fmt = ao->format == AF_FORMAT_FLOAT_LE ?
WAV_ID_FLOAT_PCM : WAV_ID_PCM;
uint32_t fmt_chunk_size = use_waveex ? 40 : 16;
int bits = af_fmt2bits(ao_data.format);
int bits = af_fmt2bits(ao->format);
// Master RIFF chunk
fput32le(WAV_ID_RIFF, fp);
// RIFF chunk size: 'WAVE' + 'fmt ' + 4 + fmt_chunk_size + data chunk hdr (8) + data length
// RIFF chunk size: 'WAVE' + 'fmt ' + 4 + fmt_chunk_size +
// data chunk hdr (8) + data length
fput32le(12 + fmt_chunk_size + 8 + data_length, fp);
fput32le(WAV_ID_WAVE, fp);
@ -93,17 +85,17 @@ static void write_wave_header(FILE *fp, uint64_t data_length) {
fput32le(WAV_ID_FMT, fp);
fput32le(fmt_chunk_size, fp);
fput16le(use_waveex ? WAV_ID_FORMAT_EXTENSIBLE : fmt, fp);
fput16le(ao_data.channels, fp);
fput32le(ao_data.samplerate, fp);
fput32le(ao_data.bps, fp);
fput16le(ao_data.channels * (bits / 8), fp);
fput16le(ao->channels, fp);
fput32le(ao->samplerate, fp);
fput32le(ao->bps, fp);
fput16le(ao->channels * (bits / 8), fp);
fput16le(bits, fp);
if (use_waveex) {
// Extension chunk
fput16le(22, fp);
fput16le(bits, fp);
switch (ao_data.channels) {
switch (ao->channels) {
case 5:
fput32le(0x0607, fp); // L R C Lb Rb
break;
@ -129,36 +121,36 @@ static void write_wave_header(FILE *fp, uint64_t data_length) {
fput32le(data_length, fp);
}
// to set/get/query special features/parameters
static int control(int cmd,void *arg){
return -1;
}
static int init(struct ao *ao, char *params)
{
struct priv *priv = talloc_zero(ao, struct priv);
ao->priv = priv;
// open & setup audio device
// return: 1=success 0=fail
static int init(int rate,int channels,int format,int flags){
int fast;
const opt_t subopts[] = {
{"waveheader", OPT_ARG_BOOL, &ao_pcm_waveheader, NULL},
{"file", OPT_ARG_MSTRZ, &ao_outputfilename, NULL},
{"fast", OPT_ARG_BOOL, &fast, NULL},
{"waveheader", OPT_ARG_BOOL, &priv->waveheader, NULL},
{"file", OPT_ARG_MSTRZ, &priv->outputfilename, NULL},
{"fast", OPT_ARG_BOOL, &fast, NULL},
{NULL}
};
// set defaults
ao_pcm_waveheader = 1;
priv->waveheader = 1;
if (subopt_parse(ao_subdevice, subopts) != 0) {
return 0;
}
if (!ao_outputfilename){
ao_outputfilename =
strdup(ao_pcm_waveheader?"audiodump.wav":"audiodump.pcm");
}
if (subopt_parse(params, subopts) != 0)
return -1;
if (ao_pcm_waveheader)
{
if (fast)
mp_msg(MSGT_AO, MSGL_WARN,
"[AO PCM] Suboption \"fast\" is deprecated.\n"
"[AO PCM] Use -novideo, or -benchmark if you want "
"faster playback with video.\n");
if (!priv->outputfilename)
priv->outputfilename =
strdup(priv->waveheader ? "audiodump.wav" : "audiodump.pcm");
if (priv->waveheader) {
// WAV files must have one of the following formats
switch(format){
switch (ao->format) {
case AF_FORMAT_U8:
case AF_FORMAT_S16_LE:
case AF_FORMAT_S24_LE:
@ -168,110 +160,97 @@ static int init(int rate,int channels,int format,int flags){
case AF_FORMAT_AC3_LE:
break;
default:
format = AF_FORMAT_S16_LE;
ao->format = AF_FORMAT_S16_LE;
break;
}
}
ao_data.outburst = 65536;
ao_data.buffersize= 2*65536;
ao_data.channels=channels;
ao_data.samplerate=rate;
ao_data.format=format;
ao_data.bps=channels*rate*(af_fmt2bits(format)/8);
ao->outburst = 65536;
ao->buffersize = 2 * 65536;
ao->bps = ao->channels * ao->samplerate * (af_fmt2bits(ao->format) / 8);
mp_tmsg(MSGT_AO, MSGL_INFO, "[AO PCM] File: %s (%s)\nPCM: Samplerate: %iHz Channels: %s Format %s\n", ao_outputfilename,
(ao_pcm_waveheader?"WAVE":"RAW PCM"), rate,
(channels > 1) ? "Stereo" : "Mono", af_fmt2str_short(format));
mp_tmsg(MSGT_AO, MSGL_INFO, "[AO PCM] Info: Faster dumping is achieved with -novideo -ao pcm:fast\n[AO PCM] Info: To write WAVE files use -ao pcm:waveheader (default).\n");
mp_tmsg(MSGT_AO, MSGL_INFO, "[AO PCM] File: %s (%s)\n"
"PCM: Samplerate: %d Hz Channels: %d Format: %s\n",
priv->outputfilename,
priv->waveheader ? "WAVE" : "RAW PCM", ao->samplerate,
ao->channels, af_fmt2str_short(ao->format));
mp_tmsg(MSGT_AO, MSGL_INFO,
"[AO PCM] Info: Faster dumping is achieved with -novideo\n"
"[AO PCM] Info: To write WAVE files use -ao pcm:waveheader (default).\n");
fp = fopen(ao_outputfilename, "wb");
if(fp) {
if(ao_pcm_waveheader){ /* Reserve space for wave header */
write_wave_header(fp, 0x7ffff000);
}
return 1;
priv->fp = fopen(priv->outputfilename, "wb");
if (!priv->fp) {
mp_tmsg(MSGT_AO, MSGL_ERR, "[AO PCM] Failed to open %s for writing!\n",
priv->outputfilename);
return -1;
}
mp_tmsg(MSGT_AO, MSGL_ERR, "[AO PCM] Failed to open %s for writing!\n",
ao_outputfilename);
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(int immed){
static void uninit(struct ao *ao, bool cut_audio)
{
struct priv *priv = ao->priv;
if(ao_pcm_waveheader){ /* Rewrite wave header */
int broken_seek = 0;
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 = GetFileType((HANDLE)_get_osfhandle(_fileno(fp))) != FILE_TYPE_DISK;
// 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(fp, 0, SEEK_SET) != 0)
mp_msg(MSGT_AO, MSGL_ERR, "Could not seek to start, WAV size headers not updated!\n");
if (broken_seek || fseek(priv->fp, 0, SEEK_SET) != 0)
mp_msg(MSGT_AO, MSGL_ERR, "Could not seek to start, "
"WAV size headers not updated!\n");
else {
if (data_length > 0xfffff000) {
mp_msg(MSGT_AO, MSGL_ERR, "File larger than allowed for WAV files, may play truncated!\n");
data_length = 0xfffff000;
if (priv->data_length > 0xfffff000) {
mp_msg(MSGT_AO, MSGL_ERR, "File larger than allowed for "
"WAV files, may play truncated!\n");
priv->data_length = 0xfffff000;
}
write_wave_header(fp, data_length);
write_wave_header(ao, priv->fp, priv->data_length);
}
}
fclose(fp);
free(ao_outputfilename);
ao_outputfilename = NULL;
fclose(priv->fp);
free(priv->outputfilename);
}
// stop playing and empty buffers (for seeking/pause)
static void reset(void){
}
// stop playing, keep buffers (for pause)
static void audio_pause(void)
static int get_space(struct ao *ao)
{
// for now, just call reset();
reset();
return ao->outburst;
}
// resume playing, after audio_pause()
static void audio_resume(void)
static int play(struct ao *ao, void *data, int len, int flags)
{
}
struct priv *priv = ao->priv;
// return: how many bytes can be played without blocking
static int get_space(void){
if(vo_pts)
return ao_data.pts < vo_pts + fast * 30000 ? ao_data.outburst : 0;
return ao_data.outburst;
}
// plays 'len' bytes of 'data'
// it should round it down to outburst*n
// return: number of bytes played
static int play(void* data,int len,int flags){
if (ao_data.channels == 5 || ao_data.channels == 6 || ao_data.channels == 8) {
int frame_size = af_fmt2bits(ao_data.format) / 8;
len -= len % (frame_size * ao_data.channels);
if (ao->channels == 5 || ao->channels == 6 || ao->channels == 8) {
int frame_size = af_fmt2bits(ao->format) / 8;
len -= len % (frame_size * ao->channels);
reorder_channel_nch(data, AF_CHANNEL_LAYOUT_MPLAYER_DEFAULT,
AF_CHANNEL_LAYOUT_WAVEEX_DEFAULT,
ao_data.channels,
len / frame_size, frame_size);
ao->channels, len / frame_size, frame_size);
}
//printf("PCM: Writing chunk!\n");
fwrite(data,len,1,fp);
if(ao_pcm_waveheader)
data_length += len;
fwrite(data, len, 1, priv->fp);
priv->data_length += len;
return len;
}
// return: delay in seconds between first and last sample in buffer
static float get_delay(void){
return 0.0;
}
const struct ao_driver audio_out_pcm = {
.is_new = true,
.info = &(const struct ao_info) {
"RAW PCM/WAVE file writer audio output",
"pcm",
"Atmosfear",
"",
},
.init = init,
.uninit = uninit,
.get_space = get_space,
.play = play,
};

View File

@ -236,6 +236,10 @@ int ao_control(struct ao *ao, int cmd, void *arg)
double ao_get_delay(struct ao *ao)
{
if (!ao->driver->get_delay) {
assert(ao->untimed);
return 0;
}
return ao->driver->get_delay(ao);
}
@ -246,17 +250,20 @@ int ao_get_space(struct ao *ao)
void ao_reset(struct ao *ao)
{
ao->driver->reset(ao);
if (ao->driver->reset)
ao->driver->reset(ao);
}
void ao_pause(struct ao *ao)
{
ao->driver->pause(ao);
if (ao->driver->pause)
ao->driver->pause(ao);
}
void ao_resume(struct ao *ao)
{
ao->driver->resume(ao);
if (ao->driver->resume)
ao->driver->resume(ao);
}

View File

@ -72,6 +72,7 @@ struct ao {
int buffersize;
int pts;
bool initialized;
bool untimed;
const struct ao_driver *driver;
void *priv;
};

View File

@ -2048,7 +2048,7 @@ static int check_framedrop(struct MPContext *mpctx, double frame_time) {
struct MPOpts *opts = &mpctx->opts;
// check for frame-drop:
current_module = "check_framedrop";
if (mpctx->sh_audio && !mpctx->d_audio->eof) {
if (mpctx->sh_audio && !mpctx->ao->untimed && !mpctx->d_audio->eof) {
static int dropped_frames;
float delay = opts->playback_speed * ao_get_delay(mpctx->ao);
float d = delay-mpctx->delay;
@ -2411,6 +2411,9 @@ static int fill_audio_out_buffers(struct MPContext *mpctx)
current_module="play_audio";
if (ao->untimed && mpctx->sh_video && mpctx->delay > 0)
return 0;
// all the current uses of ao->pts seem to be in aos that handle
// sync completely wrong; there should be no need to use ao->pts
// in get_space()
@ -3366,7 +3369,7 @@ static void run_playloop(struct MPContext *mpctx)
if (mpctx->sh_audio && !mpctx->paused
&& (!mpctx->restart_playback || !mpctx->sh_video)) {
int status = fill_audio_out_buffers(mpctx);
full_audio_buffers = status >= 0;
full_audio_buffers = status >= 0 && !mpctx->ao->untimed;
if (status == -2)
// at eof, all audio at least written to ao
if (!mpctx->sh_video)
@ -3412,7 +3415,9 @@ static void run_playloop(struct MPContext *mpctx)
} else if (!mpctx->stop_play) {
int sleep_time = 100;
if (mpctx->sh_audio) {
if (full_audio_buffers)
if (mpctx->ao->untimed)
sleep_time = 0;
else if (full_audio_buffers)
sleep_time = FFMAX(20, a_buf * 1000 - 50);
else
sleep_time = 20;