Hi Sam, thanks for opening up the source code, that's really cool! I hope someone can benefit from your experimentation. I'm curious, if iPhone is said to have the same OS as OSX (apart from the Cocoa layer where iPhone uses Cocoa Touch), then PortAudio should also run unmodified there. Have you tried that? Cheers, Benny On 3/25/08, Samuel Vinson <samuelv at laposte.net> wrote: > Hi, > > I'm joining the implementation of pjmedia for ipod (ipodsound.c). > I didn't test on official sdk because I haven't get Mac. I'm hoping I > can borrow friday, and test this weekend. > > This implementation works on ipod > - playfile ok > - recfile ko > - pjsua ok (sound is bi-directional/full duplex) > > This implementation doesn't work on iphone 1.1.2, 1.1.3 and 1.1.4. > When you read the doc of official sdk, this code should work. I'm > waitinig to test or receive result of someone. > > > Samuel > > > > /* > * Copyright (C) 2007-2008 Samuel Vinson <samuelv at users.sourceforge.net> > * > * This program 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 of the License, or > * (at your option) any later version. > * > * This program 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 this program; if not, write to the Free Software > * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA > */ > #if PJMEDIA_SOUND_IMPLEMENTATION==PJMEDIA_SOUND_IPOD_SOUND > > #include <pjmedia/sound.h> > #include <pjmedia/errno.h> > #include <pj/assert.h> > #include <pj/pool.h> > #include <pj/log.h> > > #import <AudioToolbox/AudioToolbox.h> > > #define THIS_FILE "ipodsound.c" > #define BITS_PER_SAMPLE 16 > #define BYTES_PER_SAMPLE (BITS_PER_SAMPLE/8) > #define AUDIO_BUFFERS 3 > > #if 1 > # define TRACE_(x) PJ_LOG(1,x) > #else > # define TRACE_(x) > #endif > > static pjmedia_snd_dev_info ipod_snd_dev_info = > { > "iPod Sound Device", > 1, > 1, > 8000 > }; > > static pj_pool_factory *snd_pool_factory; > > typedef struct AQStruct > { > AudioQueueRef queue; > AudioQueueBufferRef mBuffers[AUDIO_BUFFERS]; > AudioStreamBasicDescription mDataFormat; > } AQStruct; > > struct pjmedia_snd_stream > { > pj_pool_t *pool; > pjmedia_dir dir; > int rec_id; > int play_id; > unsigned clock_rate; > unsigned channel_count; > unsigned samples_per_frame; > unsigned bits_per_sample; > > pjmedia_snd_rec_cb rec_cb; > pjmedia_snd_play_cb play_cb; > > void *user_data; > > AQStruct *play_strm; /**< Playback stream. */ > AQStruct *rec_strm; /**< Capture stream. */ > > pj_uint16_t *play_buffer; > > pj_uint32_t timestamp; > }; > > static void playAQBufferCallback( > void *userData, > AudioQueueRef outQ, > AudioQueueBufferRef outQB) > { > pjmedia_snd_stream *play_strm = userData; > > pj_status_t status; > > // inData = (AQPlayerStruct *)in; > // if (inData->frameCount > 0) > { > /* Calculate bytes per frame */ > outQB->mAudioDataByteSize = play_strm->samples_per_frame * //BYTES_PER_SAMPLE; > play_strm->bits_per_sample / 8; > /* Get frame from application. */ > if (play_strm->channel_count == 1) > { > pj_uint16_t *in, *out; > int i; > status = (*play_strm->play_cb)(play_strm->user_data, > play_strm->timestamp, > play_strm->play_buffer, > outQB->mAudioDataByteSize); > > in = play_strm->play_buffer; > out = (pj_uint16_t *)outQB->mAudioData; > for (i = 0 ; i < play_strm->samples_per_frame; ++i) > { > *out++ = *in; > *out++ = *in++; > } > > outQB->mAudioDataByteSize *= 2; > } > else > { > status = (*play_strm->play_cb)(play_strm->user_data, > play_strm->timestamp, > (char *)outQB->mAudioData, > outQB->mAudioDataByteSize); > } > > play_strm->timestamp += play_strm->samples_per_frame; > AudioQueueEnqueueBuffer(outQ, outQB, 0, NULL); > > if (status != PJ_SUCCESS) > { > PJ_LOG(1, (THIS_FILE, "playAQBufferCallback err %d\n", status)); > } > } > } > > static void recAQBufferCallback ( > void *userData, > AudioQueueRef inQ, > AudioQueueBufferRef inQB, > const AudioTimeStamp *inStartTime, > UInt32 inNumPackets, > const AudioStreamPacketDescription *inPacketDesc) > { > pj_status_t status; > pj_uint32_t bytes_per_frame; > pjmedia_snd_stream *rec_strm = userData; > > bytes_per_frame = rec_strm->samples_per_frame * > rec_strm->bits_per_sample / 8; > > > status = rec_strm->rec_cb(rec_strm->user_data, rec_strm->timestamp, > (void*)inQB->mAudioData, bytes_per_frame); > > rec_strm->timestamp += rec_strm->samples_per_frame; > AudioQueueEnqueueBuffer(/*pAqData->mQueue*/inQ, inQB, 0, NULL); > > if(status != PJ_SUCCESS) > { > PJ_LOG(1, (THIS_FILE, "recAQBufferCallback err %d\n", status)); > return; > } > } > > PJ_DEF(pj_status_t) pjmedia_snd_init(pj_pool_factory *factory) > { > TRACE_((THIS_FILE, "pjmedia_snd_init.")); > > snd_pool_factory = factory; > > return PJ_SUCCESS; > } > > PJ_DEF(pj_status_t) pjmedia_snd_deinit(void) > { > TRACE_((THIS_FILE, "pjmedia_snd_deinit.")); > > snd_pool_factory = NULL; > > return PJ_SUCCESS; > } > > PJ_DEF(int) pjmedia_snd_get_dev_count(void) > { > TRACE_((THIS_FILE, "pjmedia_snd_get_dev_count.")); > /* Always return 1 */ > return 1; > } > > PJ_DEF(const pjmedia_snd_dev_info*) pjmedia_snd_get_dev_info(unsigned index) > { > TRACE_((THIS_FILE, "pjmedia_snd_get_dev_info %d.", index)); > /* Always return the default sound device */ > PJ_ASSERT_RETURN(index==0 || index==(unsigned)-1, NULL); > return &ipod_snd_dev_info; > } > > PJ_DEF(pj_status_t) pjmedia_snd_open_rec( int index, > unsigned clock_rate, > unsigned channel_count, > unsigned samples_per_frame, > unsigned bits_per_sample, > pjmedia_snd_rec_cb rec_cb, > void *user_data, > pjmedia_snd_stream **p_snd_strm) > { > return pjmedia_snd_open(index, -2, clock_rate, channel_count, > samples_per_frame, bits_per_sample, > rec_cb, NULL, user_data, p_snd_strm); > } > > PJ_DEF(pj_status_t) pjmedia_snd_open_player( int index, > unsigned clock_rate, > unsigned channel_count, > unsigned samples_per_frame, > unsigned bits_per_sample, > pjmedia_snd_play_cb play_cb, > void *user_data, > pjmedia_snd_stream **p_snd_strm ) > { > return pjmedia_snd_open(-2, index, clock_rate, channel_count, > samples_per_frame, bits_per_sample, > NULL, play_cb, user_data, p_snd_strm); > } > > PJ_DEF(pj_status_t) pjmedia_snd_open( int rec_id, > int play_id, > unsigned clock_rate, > unsigned channel_count, > unsigned samples_per_frame, > unsigned bits_per_sample, > pjmedia_snd_rec_cb rec_cb, > pjmedia_snd_play_cb play_cb, > void *user_data, > pjmedia_snd_stream **p_snd_strm) > { > pj_pool_t *pool; > pjmedia_snd_stream *snd_strm; > AQStruct *aq; > > /* Make sure sound subsystem has been initialized with > * pjmedia_snd_init() */ > PJ_ASSERT_RETURN( snd_pool_factory != NULL, PJ_EINVALIDOP ); > > /* Can only support 16bits per sample */ > PJ_ASSERT_RETURN(bits_per_sample == BITS_PER_SAMPLE, PJ_EINVAL); > > pool = pj_pool_create(snd_pool_factory, NULL, 128, 128, NULL); > snd_strm = PJ_POOL_ZALLOC_T(pool, pjmedia_snd_stream); > > snd_strm->pool = pool; > > if (rec_id == -1) rec_id = 0; > if (play_id == -1) play_id = 0; > > if (rec_id != -2 && play_id != -2) > snd_strm->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK; > else if (rec_id != -2) > snd_strm->dir = PJMEDIA_DIR_CAPTURE; > else if (play_id != -2) > snd_strm->dir = PJMEDIA_DIR_PLAYBACK; > > snd_strm->rec_id = rec_id; > snd_strm->play_id = play_id; > snd_strm->clock_rate = clock_rate; > snd_strm->channel_count = channel_count; > snd_strm->samples_per_frame = samples_per_frame; > snd_strm->bits_per_sample = bits_per_sample; > snd_strm->rec_cb = rec_cb; > snd_strm->play_cb = play_cb; > snd_strm->play_buffer = NULL; > snd_strm->user_data = user_data; > > /* Create player stream */ > if (snd_strm->dir & PJMEDIA_DIR_PLAYBACK) > { > aq = snd_strm->play_strm = PJ_POOL_ZALLOC_T(pool, AQStruct); > // Set up our audio format -- signed interleaved shorts (-32767 -> 32767), 16 bit stereo > // The iphone does not want to play back float32s. > aq->mDataFormat.mSampleRate = (Float64)clock_rate; // 8000 / 44100 > aq->mDataFormat.mFormatID = kAudioFormatLinearPCM; > aq->mDataFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | > kAudioFormatFlagIsPacked; > // this means each packet in the AQ has two samples, one for each > // channel -> 4 bytes/frame/packet > // In uncompressed audio, each packet contains exactly one frame. > aq->mDataFormat.mFramesPerPacket = 1; > aq->mDataFormat.mBytesPerFrame = aq->mDataFormat.mBytesPerPacket = > 2 * bits_per_sample / 8; > aq->mDataFormat.mChannelsPerFrame = 2; > if (channel_count == 1) > { > snd_strm->play_buffer = pj_pool_zalloc(pool, > samples_per_frame * bits_per_sample / 8); > } > > aq->mDataFormat.mBitsPerChannel = 16; // FIXME > } > > /* Create capture stream */ > if (snd_strm->dir & PJMEDIA_DIR_CAPTURE) > { > aq = snd_strm->rec_strm = PJ_POOL_ZALLOC_T(pool, AQStruct); > // TODO allocate buffers ?? > aq->mDataFormat.mSampleRate = (Float64)clock_rate; > aq->mDataFormat.mFormatID = kAudioFormatLinearPCM; > aq->mDataFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | > kLinearPCMFormatFlagIsPacked; > // In uncompressed audio, each packet contains exactly one frame. > aq->mDataFormat.mFramesPerPacket = 1; > aq->mDataFormat.mBytesPerPacket = aq->mDataFormat.mBytesPerFrame = > channel_count * bits_per_sample / 8; > aq->mDataFormat.mChannelsPerFrame = channel_count; > aq->mDataFormat.mBitsPerChannel = 16; // FIXME > } > > *p_snd_strm = snd_strm; > > return PJ_SUCCESS; > } > > /** > */ > PJ_DEF(pj_status_t) pjmedia_snd_stream_start(pjmedia_snd_stream *stream) > { > OSStatus status; > pj_int32_t i; > AQStruct *aq; > > TRACE_((THIS_FILE, "pjmedia_snd_stream_start.")); > if (stream->dir & PJMEDIA_DIR_PLAYBACK) > { > TRACE_((THIS_FILE, "pjmedia_snd_stream_start : play back starting...")); > // FIXME : move in pjmedia_snd_open > aq = stream->play_strm; > status = AudioQueueNewOutput(&(aq->mDataFormat), > playAQBufferCallback, > stream, > NULL, > kCFRunLoopCommonModes, > 0, > &(aq->queue)); > if (status) > { > PJ_LOG(1, (THIS_FILE, "AudioQueueNewOutput err %d", status)); > return PJMEDIA_ERROR; // FIXME > } > > UInt32 bufferBytes = stream->samples_per_frame * > stream->bits_per_sample / 8 * aq->mDataFormat.mBytesPerFrame; > for (i=0; i<AUDIO_BUFFERS; i++) > { > status = AudioQueueAllocateBuffer(aq->queue, bufferBytes, > &(aq->mBuffers[i])); > if (status) > { > PJ_LOG(1, (THIS_FILE, > "AudioQueueAllocateBuffer[%d] err %d\n",i, status)); > // return PJMEDIA_ERROR; // FIXME return ??? > } > /* "Prime" by calling the callback once per buffer */ > playAQBufferCallback (stream, aq->queue, aq->mBuffers[i]); > } > // FIXME: END > > status = AudioQueueStart(aq->queue, NULL); > if (status) > { > PJ_LOG(1, (THIS_FILE, > "AudioQueueStart err %d\n", status)); > } > TRACE_((THIS_FILE, "pjmedia_snd_stream_start : play back started")); > } > if (stream->dir & PJMEDIA_DIR_CAPTURE) > { > TRACE_((THIS_FILE, "pjmedia_snd_stream_start : capture starting...")); > // FIXME > aq = stream->rec_strm; > > status = AudioQueueNewInput (&(aq->mDataFormat), > recAQBufferCallback, > stream, > NULL, > kCFRunLoopCommonModes, > 0, > &(aq->queue)); > if (status) > { > PJ_LOG(1, (THIS_FILE, "AudioQueueNewInput err %d", status)); > return PJMEDIA_ERROR; // FIXME > } > > UInt32 bufferBytes; > bufferBytes = stream->samples_per_frame * > stream->bits_per_sample / 8; > for (i = 0; i < AUDIO_BUFFERS; ++i) > { > status = AudioQueueAllocateBuffer (aq->queue, bufferBytes, > &(aq->mBuffers[i])); > > if (status) > { > PJ_LOG(1, (THIS_FILE, > "AudioQueueAllocateBuffer[%d] err %d\n",i, status)); > // return PJMEDIA_ERROR; // FIXME return ??? > } > AudioQueueEnqueueBuffer (aq->queue, aq->mBuffers[i], 0, NULL); > } > > // FIXME : END > > status = AudioQueueStart (aq->queue, NULL); > if (status) > { > PJ_LOG(1, (THIS_FILE, "Starting capture stream error %d", status)); > return PJMEDIA_ENOSNDREC; > } > > UInt32 level = 1; > status = AudioQueueSetProperty(aq->queue, > kAudioQueueProperty_EnableLevelMetering, &level, sizeof(level)); > if (status) > { > PJ_LOG(1, (THIS_FILE, "AudioQueueSetProperty err %d", status)); > } > > TRACE_((THIS_FILE, "pjmedia_snd_stream_start : capture started...")); > } > > return PJ_SUCCESS; > } > > /** > */ > PJ_DEF(pj_status_t) pjmedia_snd_stream_stop(pjmedia_snd_stream *stream) > { > OSStatus status; > AQStruct *aq; > // int i; > > TRACE_((THIS_FILE, "pjmedia_snd_stream_stop.")); > if (stream->dir & PJMEDIA_DIR_PLAYBACK) > { > PJ_LOG(5,(THIS_FILE, "Stopping playback stream")); > aq = stream->play_strm; > status = AudioQueuePause (aq->queue); > if (status) > PJ_LOG(1, (THIS_FILE, "Stopping playback stream error %d", status)); > } > if (stream->dir & PJMEDIA_DIR_CAPTURE) > { > PJ_LOG(5,(THIS_FILE, "Stopping capture stream")); > aq = stream->rec_strm; > status = AudioQueuePause (aq->queue); > if (status) > PJ_LOG(1, (THIS_FILE, "Stopping capture stream error %d", status)); > } > return PJ_SUCCESS; > } > > /** > */ > PJ_DEF(pj_status_t) pjmedia_snd_stream_get_info(pjmedia_snd_stream *strm, > pjmedia_snd_stream_info *pi) > { > PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL); > TRACE_((THIS_FILE, "pjmedia_snd_stream_get_info.")); > > pj_bzero(pi, sizeof(pjmedia_snd_stream_info)); > pi->dir = strm->dir; > pi->play_id = strm->play_id; > pi->rec_id = strm->rec_id; > pi->clock_rate = strm->clock_rate; > pi->channel_count = strm->channel_count; > pi->samples_per_frame = strm->samples_per_frame; > pi->bits_per_sample = strm->bits_per_sample; > pi->rec_latency = 0; > pi->play_latency = 0; > > return PJ_SUCCESS; > } > > > PJ_DEF(pj_status_t) pjmedia_snd_stream_close(pjmedia_snd_stream *stream) > { > OSStatus status; > AQStruct *aq; > int i; > > TRACE_((THIS_FILE, "pjmedia_snd_stream_close.")); > if (stream->dir & PJMEDIA_DIR_PLAYBACK) > { > PJ_LOG(5,(THIS_FILE, "Disposing playback stream")); > aq = stream->play_strm; > status = AudioQueueStop (aq->queue, true); > for (i=0; i<AUDIO_BUFFERS; i++) > { > AudioQueueFreeBuffer(aq->queue,aq->mBuffers[i]); > } > status = AudioQueueDispose (aq->queue, true); > if (status) > PJ_LOG(1, (THIS_FILE, "Disposing playback stream error %d", status)); > } > if (stream->dir & PJMEDIA_DIR_CAPTURE) > { > PJ_LOG(5,(THIS_FILE, "Disposing capture stream")); > aq = stream->rec_strm; > status = AudioQueueStop (aq->queue, true); > for (i=0; i<AUDIO_BUFFERS; i++) > { > AudioQueueFreeBuffer(aq->queue,aq->mBuffers[i]); > } > > status = AudioQueueDispose (aq->queue, true); > if (status) > PJ_LOG(1, (THIS_FILE, "Disposing capture stream error %d", status)); > } > return PJ_SUCCESS; > } > > #endif /* PJMEDIA_SOUND_IMPLEMENTATION */ > > > # @configure_input@ > > # PJMEDIA features exclusion > export CFLAGS += @ac_no_small_filter@ @ac_no_large_filter@ @ac_no_speex_aec@ > > # Define the desired sound device backend > # Valid values are: > # - pa_unix: PortAudio on Unix (OSS or ALSA) > # - pa_darwinos: PortAudio on MacOSX (CoreAudio) > # - pa_old_darwinos: PortAudio on MacOSX (old CoreAudio, for OSX 10.2) > # - pa_win32: PortAudio on Win32 (WMME) > # - ds: Win32 DirectSound (dsound.c) > # - ipod: sound device on ipod (ipodsound.c) > # - null: Null sound device (nullsound.c) > AC_PJMEDIA_SND=@ac_pjmedia_snd@ > > # For Unix, specify if ALSA should be supported > AC_PA_USE_ALSA=@ac_pa_use_alsa@ > > # Additional PortAudio CFLAGS are in @ac_pa_cflags@ > > # > # Codecs > # > AC_NO_G711_CODEC=@ac_no_g711_codec@ > AC_NO_L16_CODEC=@ac_no_l16_codec@ > AC_NO_GSM_CODEC=@ac_no_gsm_codec@ > AC_NO_SPEEX_CODEC=@ac_no_speex_codec@ > AC_NO_ILBC_CODEC=@ac_no_ilbc_codec@ > AC_NO_G722_CODEC=@ac_no_g722_codec@ > > export CODEC_OBJS= > > ifeq ($(AC_NO_G711_CODEC),1) > export CFLAGS += -DPJMEDIA_HAS_G711_CODEC=0 > else > export CODEC_OBJS += > endif > > ifeq ($(AC_NO_L16_CODEC),1) > export CFLAGS += -DPJMEDIA_HAS_L16_CODEC=0 > else > export CODEC_OBJS += l16.o > endif > > ifeq ($(AC_NO_GSM_CODEC),1) > export CFLAGS += -DPJMEDIA_HAS_GSM_CODEC=0 > else > export CODEC_OBJS += gsm.o > endif > > ifeq ($(AC_NO_SPEEX_CODEC),1) > export CFLAGS += -DPJMEDIA_HAS_SPEEX_CODEC=0 > else > export CFLAGS += -I$(THIRD_PARTY)/build/speex -I$(THIRD_PARTY)/speex/include > export CODEC_OBJS += speex_codec.o > > ifneq (@ac_no_speex_aec@,1) > export PJMEDIA_OBJS += echo_speex.o > endif > > endif > > ifeq ($(AC_NO_ILBC_CODEC),1) > export CFLAGS += -DPJMEDIA_HAS_ILBC_CODEC=0 > else > export CODEC_OBJS += ilbc.o > endif > > ifeq ($(AC_NO_G722_CODEC),1) > export CFLAGS += -DPJMEDIA_HAS_G722_CODEC=0 > else > export CODEC_OBJS += g722.o g722/g722_enc.o g722/g722_dec.o > endif > > > # > # PortAudio > # > ifneq ($(findstring pa,$(AC_PJMEDIA_SND)),) > export CFLAGS += -I$(THIRD_PARTY)/build/portaudio -I$(THIRD_PARTY)/portaudio/include -DPJMEDIA_SOUND_IMPLEMENTATION=PJMEDIA_SOUND_PORTAUDIO_SOUND > export SOUND_OBJS = pasound.o > endif > > # > # Win32 DirectSound > # > ifeq ($(AC_PJMEDIA_SND),ds) > export SOUND_OBJS = dsound.o > export CFLAGS += -DPJMEDIA_SOUND_IMPLEMENTATION=PJMEDIA_SOUND_WIN32_DIRECT_SOUND > endif > > # > # iPod/iPhone > # > ifeq ($(AC_PJMEDIA_SND),ipod) > export SOUND_OBJS = ipodsound.o > export CFLAGS += -DPJMEDIA_SOUND_IMPLEMENTATION=PJMEDIA_SOUND_IPOD_SOUND \ > -D__COREAUDIO_USE_FLAT_INCLUDES__ \ > -I/usr/local/arm-apple-darwin/include/AudioToolbox \ > -I/usr/local/arm-apple-darwin/include/CoreAudio \ > -I/usr/local/arm-apple-darwin/include/CarbonCore \ > -I/usr/local/arm-apple-darwin/include/CoreServices \ > -I/usr/local/arm-apple-darwin/include/CoreFoundation > endif > > # > # Last resort, null sound device > # > ifeq ($(AC_PJMEDIA_SND),null) > export SOUND_OBJS = nullsound.o > export CFLAGS += -DPJMEDIA_SOUND_IMPLEMENTATION=PJMEDIA_SOUND_NULL_SOUND > endif > > > > > _______________________________________________ > Visit our blog: http://blog.pjsip.org > > pjsip mailing list > pjsip at lists.pjsip.org > http://lists.pjsip.org/mailman/listinfo/pjsip_lists.pjsip.org > >