Mercurial > mplayer.hg
view libao2/ao_macosx.c @ 24589:9118be6575da
demux_audio.c: Fix timestamp handling
The code calculated the pts values of audio packets by adding the length
of the current packet to the pts of the previous one. The length of the
previous packet should be added instead. This broke WAV timestamps near
the end of the stream where a short packet occurs.
Change the code to store the pts of the next packet instead of the last
one. This fixes the WAV timestamps and allows some simplifications.
MP3 timestamps are not affected as packets are always treated as
constant decoded length, and FLAC timestamps still have worse problems
(FLAC is treated as as if it was constant bitrate even though it isn't).
Also store the timestamps as double instead of float.
author | uau |
---|---|
date | Mon, 24 Sep 2007 21:49:56 +0000 |
parents | 300e9b7c499f |
children | 09f9d0de17f1 |
line wrap: on
line source
/* * * ao_macosx.c * * Original Copyright (C) Timothy J. Wood - Aug 2000 * * This file is part of libao, a cross-platform library. See * README for a history of this source code. * * libao is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * libao 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 General Public License for more details. * * You should have received a copy of the GNU General Public License along * with libao; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ /* * The MacOS X CoreAudio framework doesn't mesh as simply as some * simpler frameworks do. This is due to the fact that CoreAudio pulls * audio samples rather than having them pushed at it (which is nice * when you are wanting to do good buffering of audio). */ /* Change log: * * 14/5-2003: Ported to MPlayer libao2 by Dan Christiansen * * AC-3 and MPEG audio passthrough is possible, but I don't have * access to a sound card that supports it. */ #include <CoreServices/CoreServices.h> #include <AudioUnit/AudioUnit.h> #include <AudioToolbox/AudioToolbox.h> #include <stdio.h> #include <string.h> #include <stdlib.h> #include <inttypes.h> #include <pthread.h> #include "config.h" #include "mp_msg.h" #include "audio_out.h" #include "audio_out_internal.h" #include "libaf/af_format.h" static ao_info_t info = { "Darwin/Mac OS X native audio output", "macosx", "Timothy J. Wood & Dan Christiansen & Chris Roccati", "" }; LIBAO_EXTERN(macosx) /* Prefix for all mp_msg() calls */ #define ao_msg(a, b, c...) mp_msg(a, b, "AO: [macosx] " c) /* This is large, but best (maybe it should be even larger). * CoreAudio supposedly has an internal latency in the order of 2ms */ #define NUM_BUFS 32 typedef struct ao_macosx_s { /* AudioUnit */ AudioUnit theOutputUnit; int packetSize; int paused; /* Ring-buffer */ /* does not need explicit synchronization, but needs to allocate * (num_chunks + 1) * chunk_size memory to store num_chunks * chunk_size * data */ unsigned char *buffer; unsigned int buffer_len; ///< must always be (num_chunks + 1) * chunk_size unsigned int num_chunks; unsigned int chunk_size; unsigned int buf_read_pos; unsigned int buf_write_pos; } ao_macosx_t; static ao_macosx_t *ao = NULL; /** * \brief return number of free bytes in the buffer * may only be called by mplayer's thread * \return minimum number of free bytes in buffer, value may change between * two immediately following calls, and the real number of free bytes * might actually be larger! */ static int buf_free(void) { int free = ao->buf_read_pos - ao->buf_write_pos - ao->chunk_size; if (free < 0) free += ao->buffer_len; return free; } /** * \brief return number of buffered bytes * may only be called by playback thread * \return minimum number of buffered bytes, value may change between * two immediately following calls, and the real number of buffered bytes * might actually be larger! */ static int buf_used(void) { int used = ao->buf_write_pos - ao->buf_read_pos; if (used < 0) used += ao->buffer_len; return used; } /** * \brief add data to ringbuffer */ static int write_buffer(unsigned char* data, int len){ int first_len = ao->buffer_len - ao->buf_write_pos; int free = buf_free(); if (len > free) len = free; if (first_len > len) first_len = len; // till end of buffer memcpy (&ao->buffer[ao->buf_write_pos], data, first_len); if (len > first_len) { // we have to wrap around // remaining part from beginning of buffer memcpy (ao->buffer, &data[first_len], len - first_len); } ao->buf_write_pos = (ao->buf_write_pos + len) % ao->buffer_len; return len; } /** * \brief remove data from ringbuffer */ static int read_buffer(unsigned char* data,int len){ int first_len = ao->buffer_len - ao->buf_read_pos; int buffered = buf_used(); if (len > buffered) len = buffered; if (first_len > len) first_len = len; // till end of buffer memcpy (data, &ao->buffer[ao->buf_read_pos], first_len); if (len > first_len) { // we have to wrap around // remaining part from beginning of buffer memcpy (&data[first_len], ao->buffer, len - first_len); } ao->buf_read_pos = (ao->buf_read_pos + len) % ao->buffer_len; return len; } OSStatus theRenderProc(void *inRefCon, AudioUnitRenderActionFlags *inActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumFrames, AudioBufferList *ioData) { int amt=buf_used(); int req=(inNumFrames)*ao->packetSize; if(amt>req) amt=req; if(amt) read_buffer((unsigned char *)ioData->mBuffers[0].mData, amt); else audio_pause(); ioData->mBuffers[0].mDataByteSize = amt; return noErr; } static int control(int cmd,void *arg){ ao_control_vol_t *control_vol; OSStatus err; Float32 vol; switch (cmd) { case AOCONTROL_GET_VOLUME: control_vol = (ao_control_vol_t*)arg; err = AudioUnitGetParameter(ao->theOutputUnit, kHALOutputParam_Volume, kAudioUnitScope_Global, 0, &vol); if(err==0) { // printf("GET VOL=%f\n", vol); control_vol->left=control_vol->right=vol*100.0/4.0; return CONTROL_TRUE; } else { return CONTROL_FALSE; } case AOCONTROL_SET_VOLUME: control_vol = (ao_control_vol_t*)arg; vol=(control_vol->left+control_vol->right)*4.0/200.0; err = AudioUnitSetParameter(ao->theOutputUnit, kHALOutputParam_Volume, kAudioUnitScope_Global, 0, vol, 0); if(err==0) { // printf("SET VOL=%f\n", vol); return CONTROL_TRUE; } else { return CONTROL_FALSE; } /* Everything is currently unimplemented */ default: return CONTROL_FALSE; } } static void print_format(const char* str,AudioStreamBasicDescription *f){ uint32_t flags=(uint32_t) f->mFormatFlags; ao_msg(MSGT_AO,MSGL_V, "%s %7.1fHz %dbit [%c%c%c%c] %s %s %s%s%s%s\n", str, f->mSampleRate, f->mBitsPerChannel, (int)(f->mFormatID & 0xff000000) >> 24, (int)(f->mFormatID & 0x00ff0000) >> 16, (int)(f->mFormatID & 0x0000ff00) >> 8, (int)(f->mFormatID & 0x000000ff) >> 0, (flags&kAudioFormatFlagIsFloat) ? "float" : "int", (flags&kAudioFormatFlagIsBigEndian) ? "BE" : "LE", (flags&kAudioFormatFlagIsSignedInteger) ? "S" : "U", (flags&kAudioFormatFlagIsPacked) ? " packed" : "", (flags&kAudioFormatFlagIsAlignedHigh) ? " aligned" : "", (flags&kAudioFormatFlagIsNonInterleaved) ? " ni" : "" ); ao_msg(MSGT_AO,MSGL_DBG2, "%5d mBytesPerPacket\n", (int)f->mBytesPerPacket); ao_msg(MSGT_AO,MSGL_DBG2, "%5d mFramesPerPacket\n", (int)f->mFramesPerPacket); ao_msg(MSGT_AO,MSGL_DBG2, "%5d mBytesPerFrame\n", (int)f->mBytesPerFrame); ao_msg(MSGT_AO,MSGL_DBG2, "%5d mChannelsPerFrame\n", (int)f->mChannelsPerFrame); } static int init(int rate,int channels,int format,int flags) { AudioStreamBasicDescription inDesc; ComponentDescription desc; Component comp; AURenderCallbackStruct renderCallback; OSStatus err; UInt32 size, maxFrames; int aoIsCreated = ao != NULL; if (!aoIsCreated) ao = malloc(sizeof(ao_macosx_t)); // Build Description for the input format inDesc.mSampleRate=rate; inDesc.mFormatID=kAudioFormatLinearPCM; inDesc.mChannelsPerFrame=channels; switch(format&AF_FORMAT_BITS_MASK){ case AF_FORMAT_8BIT: inDesc.mBitsPerChannel=8; break; case AF_FORMAT_16BIT: inDesc.mBitsPerChannel=16; break; case AF_FORMAT_24BIT: inDesc.mBitsPerChannel=24; break; case AF_FORMAT_32BIT: inDesc.mBitsPerChannel=32; break; default: ao_msg(MSGT_AO, MSGL_WARN, "Unsupported format (0x%08x)\n", format); return CONTROL_FALSE; break; } if((format&AF_FORMAT_POINT_MASK)==AF_FORMAT_F) { // float inDesc.mFormatFlags = kAudioFormatFlagIsFloat|kAudioFormatFlagIsPacked; } else if((format&AF_FORMAT_SIGN_MASK)==AF_FORMAT_SI) { // signed int inDesc.mFormatFlags = kAudioFormatFlagIsSignedInteger|kAudioFormatFlagIsPacked; } else { // unsigned int inDesc.mFormatFlags = kAudioFormatFlagIsPacked; } if((format&AF_FORMAT_END_MASK)==AF_FORMAT_BE) inDesc.mFormatFlags |= kAudioFormatFlagIsBigEndian; inDesc.mFramesPerPacket = 1; ao->packetSize = inDesc.mBytesPerPacket = inDesc.mBytesPerFrame = inDesc.mFramesPerPacket*channels*(inDesc.mBitsPerChannel/8); print_format("source: ",&inDesc); if (!aoIsCreated) { desc.componentType = kAudioUnitType_Output; desc.componentSubType = kAudioUnitSubType_DefaultOutput; desc.componentManufacturer = kAudioUnitManufacturer_Apple; desc.componentFlags = 0; desc.componentFlagsMask = 0; comp = FindNextComponent(NULL, &desc); //Finds an component that meets the desc spec's if (comp == NULL) { ao_msg(MSGT_AO, MSGL_WARN, "Unable to find Output Unit component\n"); return CONTROL_FALSE; } err = OpenAComponent(comp, &(ao->theOutputUnit)); //gains access to the services provided by the component if (err) { ao_msg(MSGT_AO, MSGL_WARN, "Unable to open Output Unit component (err=%d)\n", err); return CONTROL_FALSE; } // Initialize AudioUnit err = AudioUnitInitialize(ao->theOutputUnit); if (err) { ao_msg(MSGT_AO, MSGL_WARN, "Unable to initialize Output Unit component (err=%d)\n", err); return CONTROL_FALSE; } } size = sizeof(AudioStreamBasicDescription); err = AudioUnitSetProperty(ao->theOutputUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &inDesc, size); if (err) { ao_msg(MSGT_AO, MSGL_WARN, "Unable to set the input format (err=%d)\n", err); return CONTROL_FALSE; } size = sizeof(UInt32); err = AudioUnitGetProperty(ao->theOutputUnit, kAudioDevicePropertyBufferSize, kAudioUnitScope_Input, 0, &maxFrames, &size); if (err) { ao_msg(MSGT_AO,MSGL_WARN, "AudioUnitGetProperty returned %d when getting kAudioDevicePropertyBufferSize\n", (int)err); return CONTROL_FALSE; } ao->chunk_size = maxFrames;//*inDesc.mBytesPerFrame; ao_data.samplerate = inDesc.mSampleRate; ao_data.channels = inDesc.mChannelsPerFrame; ao_data.bps = ao_data.samplerate * inDesc.mBytesPerFrame; ao_data.outburst = ao->chunk_size; ao_data.buffersize = ao_data.bps; ao->num_chunks = (ao_data.bps+ao->chunk_size-1)/ao->chunk_size; ao->buffer_len = (ao->num_chunks + 1) * ao->chunk_size; ao->buffer = aoIsCreated ? realloc(ao->buffer,(ao->num_chunks + 1)*ao->chunk_size) : calloc(ao->num_chunks + 1, ao->chunk_size); ao_msg(MSGT_AO,MSGL_V, "using %5d chunks of %d bytes (buffer len %d bytes)\n", (int)ao->num_chunks, (int)ao->chunk_size, (int)ao->buffer_len); renderCallback.inputProc = theRenderProc; renderCallback.inputProcRefCon = 0; err = AudioUnitSetProperty(ao->theOutputUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &renderCallback, sizeof(AURenderCallbackStruct)); if (err) { ao_msg(MSGT_AO, MSGL_WARN, "Unable to set the render callback (err=%d)\n", err); return CONTROL_FALSE; } reset(); return CONTROL_OK; } static int play(void* output_samples,int num_bytes,int flags) { int wrote=write_buffer(output_samples, num_bytes); audio_resume(); return wrote; } /* set variables and buffer to initial state */ static void reset(void) { audio_pause(); /* reset ring-buffer state */ ao->buf_read_pos=0; ao->buf_write_pos=0; return; } /* return available space */ static int get_space(void) { return buf_free(); } /* return delay until audio is played */ static float get_delay(void) { int buffered = ao->buffer_len - ao->chunk_size - buf_free(); // could be less // inaccurate, should also contain the data buffered e.g. by the OS return (float)(buffered)/(float)ao_data.bps; } /* unload plugin and deregister from coreaudio */ static void uninit(int immed) { if (!immed) { long long timeleft=(1000000LL*buf_used())/ao_data.bps; ao_msg(MSGT_AO,MSGL_DBG2, "%d bytes left @%d bps (%ld usec)\n", buf_used(), ao_data.bps, (int)timeleft); usec_sleep((int)timeleft); } AudioOutputUnitStop(ao->theOutputUnit); AudioUnitUninitialize(ao->theOutputUnit); CloseComponent(ao->theOutputUnit); free(ao->buffer); free(ao); ao = NULL; } /* stop playing, keep buffers (for pause) */ static void audio_pause(void) { OSErr status=noErr; /* stop callback */ status=AudioOutputUnitStop(ao->theOutputUnit); if (status) ao_msg(MSGT_AO,MSGL_WARN, "AudioOutputUnitStop returned %d\n", (int)status); ao->paused=1; } /* resume playing, after audio_pause() */ static void audio_resume(void) { if(ao->paused) { OSErr status=noErr; /* start callback */ status=AudioOutputUnitStart(ao->theOutputUnit); if (status) ao_msg(MSGT_AO,MSGL_WARN, "AudioOutputUnitStart returned %d\n", (int)status); ao->paused=0; } }