c++ - Decoding and playing audio with ffmpeg and XAudio2 - frequency raito wrong -
i'm using ffmpeg decode audio , output using xaudio2 api, works , plays synced video output using pts. it's high pitched (i.e. sounds chipmunks).
setting breakpoints can see has sets correct sample rate audio codec in createsourcevoice. i'm stumped.
any appreciated.
#include "dvdaudiodevice.h" handle m_hbufferendevent; cdvdaudio::cdvdaudio() { m_pxaudio2 = null; m_pmasteringvoice = null; m_psourcevoice = null; m_pwfx = null; m_voicecallback = null; m_hbufferendevent = createevent(null, false, false, "buffer end event"); } cdvdaudio::~cdvdaudio() { m_pxaudio2 = null; m_pmasteringvoice = null; m_psourcevoice = null; m_pwfx = null; m_voicecallback = null; closehandle(m_hbufferendevent); m_hbufferendevent = null; } bool cdvdaudio::create(int ichannels, int ibitrate, int ibitspersample, bool bpasstrough) { coinitializeex(null, coinit_multithreaded); hresult hr = xaudio2create( &m_pxaudio2, 0, xaudio2_default_processor); if (succeeded(hr)) { m_pxaudio2->createmasteringvoice( &m_pmasteringvoice ); } // create source voice waveformatextensible wfx; memset(&wfx, 0, sizeof(waveformatextensible)); wfx.format.wformattag = wave_format_pcm; wfx.format.nsamplespersec = ibitrate;//pffmpegdata->paudiocodecctx->sample_rate;//48000 default wfx.format.nchannels = ichannels;//pffmpegdata->paudiocodecctx->channels; wfx.format.wbitspersample = 16; wfx.format.nblockalign = wfx.format.nchannels*16/8; wfx.format.navgbytespersec = wfx.format.nsamplespersec * wfx.format.nblockalign; wfx.format.cbsize = sizeof(waveformatextensible)-sizeof(waveformatex); wfx.samples.wvalidbitspersample = wfx.format.wbitspersample; if(wfx.format.nchannels == 1) { wfx.dwchannelmask = speaker_mono; } else if(wfx.format.nchannels == 2) { wfx.dwchannelmask = speaker_stereo; } else if(wfx.format.nchannels == 5) { wfx.dwchannelmask = speaker_5point1; } wfx.subformat = ksdataformat_subtype_pcm; unsigned int flags = 0;//xaudio2_voice_nosrc;// | xaudio2_voice_nopitch; //source voice m_voicecallback = new streamingvoicecallback(this); hr = m_pxaudio2->createsourcevoice(&m_psourcevoice,(waveformatex*)&wfx, 0 , 1.0f, m_voicecallback); if(!succeeded(hr)) return false; // start sound hr = m_psourcevoice->start(0); if(!succeeded(hr)) return false; return true; } dword cdvdaudio::addpackets(unsigned char* data, dword len) { memset(&m_soundbuffer,0,sizeof(xaudio2_buffer)); m_soundbuffer.audiobytes = len; m_soundbuffer.paudiodata = data; m_soundbuffer.pcontext = null;//(void*)data; xaudio2_voice_state state; while(m_psourcevoice->getstate( &state ), state.buffersqueued > 60) { waitforsingleobject( m_hbufferendevent, infinite ); } m_psourcevoice->submitsourcebuffer( &m_soundbuffer ); return 0; } void cdvdaudio::destroy() { m_pmasteringvoice->destroyvoice(); m_pxaudio2->release(); m_psourcevoice->destroyvoice(); delete m_voicecallback; m_voicecallback = null; }
#include "dvdaudiocodecffmpeg.h" #include "log.h" cdvdaudiocodecffmpeg::cdvdaudiocodecffmpeg() : cdvdaudiocodec() { m_ibuffersize = 0; m_pcodeccontext = null; m_bopenedcodec = false; } cdvdaudiocodecffmpeg::~cdvdaudiocodecffmpeg() { dispose(); } bool cdvdaudiocodecffmpeg::open(avcodecid codecid, int ichannels, int isamplerate) { avcodec* pcodec; m_bopenedcodec = false; av_register_all(); pcodec = avcodec_find_decoder(codecid); m_pcodeccontext = avcodec_alloc_context3(pcodec);//avcodec_alloc_context(); avcodec_get_context_defaults3(m_pcodeccontext, pcodec); if (!pcodec) { clog::log(logerror, "cdvdaudiocodecffmpeg::open() unable find codec"); return false; } m_pcodeccontext->debug_mv = 0; m_pcodeccontext->debug = 0; m_pcodeccontext->workaround_bugs = 1; if (pcodec->capabilities & codec_cap_truncated) m_pcodeccontext->flags |= codec_flag_truncated; m_pcodeccontext->channels = ichannels; m_pcodeccontext->sample_rate = isamplerate; //m_pcodeccontext->bits_per_sample = 24; /* //fixme brent if( extradata && extrasize > 0 ) { m_pcodeccontext->extradata_size = extrasize; m_pcodeccontext->extradata = m_dllavcodec.av_mallocz(extrasize + ff_input_buffer_padding_size); memcpy(m_pcodeccontext->extradata, extradata, extrasize); } */ // set acceleration //m_pcodeccontext->dsp_mask = ff_mm_force | ff_mm_mmx | ff_mm_mmxext | ff_mm_sse; //brent if (avcodec_open2(m_pcodeccontext, pcodec, null) < 0) { clog::log(logerror, "cdvdaudiocodecffmpeg::open() unable open codec"); dispose(); return false; } m_bopenedcodec = true; return true; } void cdvdaudiocodecffmpeg::dispose() { if (m_pcodeccontext) { if (m_bopenedcodec) avcodec_close(m_pcodeccontext); m_bopenedcodec = false; av_free(m_pcodeccontext); m_pcodeccontext = null; } m_ibuffersize = 0; } int cdvdaudiocodecffmpeg::decode(byte* pdata, int isize) { int ibytesused; if (!m_pcodeccontext) return -1; //copy ffmpeg avpacket again avpacket packet; av_init_packet(&packet); packet.data=pdata; packet.size=isize; int ioutputsize = avcodec_max_audio_frame_size; //brent ibytesused = avcodec_decode_audio3(m_pcodeccontext, (int16_t *)m_buffer, &ioutputsize/*m_ibuffersize*/, &packet); m_ibuffersize = ioutputsize;//brent return ibytesused; } int cdvdaudiocodecffmpeg::getdata(byte** dst) { *dst = m_buffer; return m_ibuffersize; } void cdvdaudiocodecffmpeg::reset() { if (m_pcodeccontext) avcodec_flush_buffers(m_pcodeccontext); } int cdvdaudiocodecffmpeg::getchannels() { if (m_pcodeccontext) return m_pcodeccontext->channels; return 0; } int cdvdaudiocodecffmpeg::getsamplerate() { if (m_pcodeccontext) return m_pcodeccontext->sample_rate; return 0; } int cdvdaudiocodecffmpeg::getbitspersample() { if (m_pcodeccontext) return 16; return 0; }
#include "dvdplayeraudio.h" #include "dvddemuxutils.h" #include "log.h" #include <assert.h> #include "dvdaudiocodecffmpeg.h" //fixme move codec factory!! cdvdplayeraudio::cdvdplayeraudio(cdvdclock* pclock) : cthread() { m_pclock = pclock; m_paudiocodec = null; m_binitializedoutputdevice = false; m_isourcechannels = 0; m_audioclock = 0; // m_currentptsitem.pts = dvd_nopts_value; // m_currentptsitem.timestamp = 0; setspeed(dvd_playspeed_normal); initializecriticalsection(&m_critcodecsection); m_messagequeue.setmaxdatasize(10 * 16 * 1024); // g_dvdperformancecounter.enableaudioqueue(&m_packetqueue); } cdvdplayeraudio::~cdvdplayeraudio() { // g_dvdperformancecounter.disableaudioqueue(); // close stream, , don't wait audio finished closestream(true); deletecriticalsection(&m_critcodecsection); } bool cdvdplayeraudio::openstream( cdemuxstreamaudio *pdemuxstream ) { // should alway's null!!!!, crash anyway when deleting m_paudiocodec here. if (m_paudiocodec) { clog::log(logfatal, "cdvdplayeraudio::openstream() m_paudiocodec != null"); return false; } avcodecid codecid = pdemuxstream->codec; clog::log(lognotice, "finding audio codec for: %i", codecid); //m_paudiocodec = cdvdfactorycodec::createaudiocodec( pdemuxstream ); m_paudiocodec = new cdvdaudiocodecffmpeg; //fixme brent codec factory needed! if (!m_paudiocodec->open(pdemuxstream->codec, pdemuxstream->ichannels, pdemuxstream->isamplerate)) { m_paudiocodec->dispose(); delete m_paudiocodec; m_paudiocodec = null; return false; } if( !m_paudiocodec ) { clog::log(logerror, "unsupported audio codec"); return false; } m_codec = pdemuxstream->codec; m_isourcechannels = pdemuxstream->ichannels; m_messagequeue.init(); clog::log(lognotice, "creating audio thread"); create(); return true; } void cdvdplayeraudio::closestream(bool bwaitforbuffers) { // wait until buffers empty if (bwaitforbuffers) m_messagequeue.waituntilempty(); // send abort message audio queue m_messagequeue.abort(); clog::log(lognotice, "waiting audio thread exit"); // shut down adio_decode thread , wait stopthread(); // set this->m_bstop true this->waitforthreadexit(infinite); // uninit queue m_messagequeue.end(); clog::log(lognotice, "deleting audio codec"); if (m_paudiocodec) { m_paudiocodec->dispose(); delete m_paudiocodec; m_paudiocodec = null; } // flush remaining pts values //flushptsqueue(); //fixme brent } void cdvdplayeraudio::onstartup() { cthread::setname("cdvdplayeraudio"); paudiopacket = null; m_audioclock = 0; audio_pkt_data = null; audio_pkt_size = 0; // g_dvdperformancecounter.enableaudiodecodeperformance(threadhandle()); } void cdvdplayeraudio::process() { clog::log(lognotice, "running thread: cdvdplayeraudio::process()"); int result; // silence data byte silence[1024]; memset(silence, 0, 1024); dvdaudioframe audioframe; __int64 iclockdiff=0; while (!m_bstop) { //don't let mess our global variables entercriticalsection(&m_critcodecsection); result = decodeframe(audioframe, m_speed != dvd_playspeed_normal); // blocks if no audio available, leaves critical section before doing leavecriticalsection(&m_critcodecsection); if( result & decode_flag_error ) { clog::log(logerror, "cdvdplayeraudio::process - decode error. skipping audio frame"); continue; } if( result & decode_flag_abort ) { clog::log(logdebug, "cdvdplayeraudio::process - abort recieved, exiting thread"); break; } if( result & decode_flag_drop ) //fixme brent { /* //frame should dropped. don't let audio move ahead of current time thou //we need able start playing @ time //when playing backwords, try keep small buffers possible // set time @ delay addptsqueue(audioframe.pts, m_dvdaudio.getdelay()); */ if (m_speed > 0) { __int64 timestamp = m_pclock->getabsoluteclock() + (audioframe.duration * dvd_playspeed_normal) / m_speed; while( !m_bstop && timestamp > m_pclock->getabsoluteclock() ) sleep(1); } continue; } if( audioframe.size > 0 ) { // have succesfully decoded audio frame, openup audio device if not done if (!m_binitializedoutputdevice) { m_binitializedoutputdevice = initializeoutputdevice(); } //add packets play m_dvdaudio.addpackets(audioframe.data, audioframe.size); // store delay pts value can calculate current playing //addptsqueue(audioframe.pts, m_dvdaudio.getdelay() - audioframe.duration);//brent } // if asked resync on packet, here if( result & decode_flag_resync ) { clog::log(logdebug, "cdvdplayeraudio::process - resync recieved."); //while (!m_bstop && (unsigned int)m_dvdaudio.getdelay() > audioframe.duration ) sleep(5); //brent m_pclock->discontinuity(clock_disc_normal, audioframe.pts); } #ifdef useoldsync //clock should calculated after packets have been added m_audioclock points //time after have been played const __int64 icurrdiff = (m_audioclock - m_dvdaudio.getdelay()) - m_pclock->getclock(); const __int64 iavdiff = (iclockdiff + icurrdiff)/2; //check discontinuity in stream, use moving average //eliminate highfreq fluctuations of large packet sizes if( abs(iavdiff) > 5000 ) // sync clock if average diff bigger 5 msec { //wait untill new audio frame wich triggered discontinuity left //then set disc state while (!m_bstop && (unsigned int)m_dvdaudio.getbytesinbuffer() > audioframe.size ) sleep(5); m_pclock->discontinuity(clock_disc_normal, m_audioclock - m_dvdaudio.getdelay()); clog::("cdvdplayer:: detected audio discontinuity, syncing clock. diff was: %i64d, %i64d, av: %i64d", iclockdiff, icurrdiff, iavdiff); iclockdiff = 0; } else { //do gradual adjustments (not working yet) //m_pclock->adjustspeedtomatch(iclock + iavdiff); iclockdiff = icurrdiff; } #endif } } void cdvdplayeraudio::onexit() { //g_dvdperformancecounter.disableaudiodecodeperformance(); // destroy audio device clog::log(lognotice, "closing audio device"); m_dvdaudio.destroy(); m_binitializedoutputdevice = false; clog::log(lognotice, "thread end: cdvdplayeraudio::onexit()"); } // decode 1 audio frame , returns uncompressed size int cdvdplayeraudio::decodeframe(dvdaudioframe &audioframe, bool bdroppacket) { cdvddemux::demuxpacket* ppacket = paudiopacket; int n=48000*2*16/8, len; //store amount left @ point, , last pts unsigned __int64 first_pkt_pts = 0; int first_pkt_size = 0; int first_pkt_used = 0; int result = 0; // make sure sent frame clean memset(&audioframe, 0, sizeof(dvdaudioframe)); if (ppacket) { first_pkt_pts = ppacket->pts; first_pkt_size = ppacket->isize; first_pkt_used = first_pkt_size - audio_pkt_size; } (;;) { /* note: audio packet can contain several frames */ while (audio_pkt_size > 0) { len = m_paudiocodec->decode(audio_pkt_data, audio_pkt_size); if (len < 0) { /* if error, skip frame */ audio_pkt_size=0; m_paudiocodec->reset(); break; } // fix fucked decoders //fixme brent if( len > audio_pkt_size ) { clog::log(logerror, "cdvdplayeraudio:decodeframe - codec tried consume more data available. potential memory corruption"); audio_pkt_size=0; m_paudiocodec->reset(); assert(0); } // decoded data , size of audioframe.size = m_paudiocodec->getdata(&audioframe.data); audio_pkt_data += len; audio_pkt_size -= len; if (audioframe.size <= 0) continue; audioframe.pts = m_audioclock; // compute duration. n = m_paudiocodec->getchannels() * m_paudiocodec->getbitspersample() / 8 * m_paudiocodec->getsamplerate(); if (n > 0) { // safety check, if channels == 0, n result in 0, , result in nice devide exception audioframe.duration = (unsigned int)(((__int64)audioframe.size * dvd_time_base) / n); // increase audioclock after packet m_audioclock += audioframe.duration; } //if asked drop packet, return size of zero. won't played //we still decode audio.. needed since still need know it's //duration make sure clock updated correctly. if( bdroppacket ) { result |= decode_flag_drop; } return result; } // free current packet if (ppacket) { cdvddemuxutils::freedemuxpacket(ppacket); //brent fixme ppacket = null; paudiopacket = null; } if (m_messagequeue.recievedabortrequest()) return decode_flag_abort; // read next packet , return -1 on error leavecriticalsection(&m_critcodecsection); //leave here might stall while cdvdmsg* pmsg; msgqueuereturncode ret = m_messagequeue.get(&pmsg, infinite); entercriticalsection(&m_critcodecsection); if (msgq_is_error(ret) || ret == msgq_abort) return decode_flag_abort; if (pmsg->istype(cdvdmsg::demuxer_packet)) { cdvdmsgdemuxerpacket* pmsgdemuxerpacket = (cdvdmsgdemuxerpacket*)pmsg; ppacket = pmsgdemuxerpacket->getpacket(); pmsgdemuxerpacket->m_ppacket = null; // xxx, test paudiopacket = ppacket; audio_pkt_data = ppacket->pdata; audio_pkt_size = ppacket->isize; } else { // other data not used here, free if // msg still available pmsg->release(); } // if update audio clock pts if (pmsg->istype(cdvdmsg::demuxer_packet) || pmsg->istype(cdvdmsg::general_resync)) { if (pmsg->istype(cdvdmsg::general_resync)) { //player asked sync on package cdvdmsggeneralresync* pmsggeneralresync = (cdvdmsggeneralresync*)pmsg; result |= decode_flag_resync; m_audioclock = pmsggeneralresync->getpts(); } else if (ppacket->pts != dvd_nopts_value) // cdvdmsg::demuxer_packet, ppacket set above { if (first_pkt_size == 0) { //first package m_audioclock = ppacket->pts; } else if (first_pkt_pts > ppacket->pts) { //okey first packet in continous stream, make sure use time here m_audioclock = ppacket->pts; } else if((unsigned __int64)m_audioclock < ppacket->pts || (unsigned __int64)m_audioclock > ppacket->pts) { //crap, moved outsided correct pts //use pts current packet, untill find better value it. //should ok after couple of frames, starts clean on packet m_audioclock = ppacket->pts; } else if(first_pkt_size == first_pkt_used) { //nice starting freshly on start of packet, use pts m_audioclock = ppacket->pts; } } } pmsg->release(); } } void cdvdplayeraudio::setspeed(int speed) { m_speed = speed; //if (m_speed == dvd_playspeed_pause) m_dvdaudio.pause(); //brent fixme //else m_dvdaudio.resume(); } bool cdvdplayeraudio::initializeoutputdevice() { int ichannels = m_paudiocodec->getchannels(); int isamplerate = m_paudiocodec->getsamplerate(); int ibitspersample = m_paudiocodec->getbitspersample(); //bool bpasstrough = m_paudiocodec->needpasstrough(); //brent if (ichannels == 0 || isamplerate == 0 || ibitspersample == 0) { clog::log(logerror, "unable create audio device, (ichannels == 0 || isamplerate == 0 || ibitspersample == 0)"); return false; } clog::log(lognotice, "creating audio device codec id: %i, channels: %i, sample rate: %i", m_codec, ichannels, isamplerate); if (m_dvdaudio.create(ichannels, isamplerate, ibitspersample, /*bpasstrough*/0)) // 16 bit ffmpeg ? //brent passthrough needed? { return true; } clog::log(logerror, "failed creating audio device codec id: %i, channels: %i, sample rate: %i", m_codec, ichannels, isamplerate); return false; }
Comments
Post a Comment