使用ffmpeg和XAudio2解码和播放音频 - 频率raito错误

时间:2016-07-11 11:37:41

标签: c++ windows audio ffmpeg xaudio2

我使用ffmpeg解码音频并使用XAudio2 API输出,它可以使用pts与视频输出同步并播放。但它的音高很高(即听起来像花栗鼠)。

设置断点我可以看到它已经从CreateSourceVoice中的音频编解码器设置了正确的采样率。我很难过。

非常感谢任何帮助。

#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 by 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 to 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 to 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 into a 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 to a 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 the stream, and don't wait for the audio to be finished CloseStream(true); DeleteCriticalSection(&m_critCodecSection); } bool CDVDPlayerAudio::OpenStream( CDemuxStreamAudio *pDemuxStream ) { // should alway's be NULL!!!!, it will probably 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 are empty if (bWaitForBuffers) m_messageQueue.WaitUntilEmpty(); // send abort message to the audio queue m_messageQueue.Abort(); CLog::Log(LOGNOTICE, "waiting for audio thread to exit"); // shut down the adio_decode thread and wait for it StopThread(); // will set this->m_bStop to 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 any 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 anybody mess with our global variables EnterCriticalSection(&m_critCodecSection); result = DecodeFrame(audioframe, m_speed != DVD_PLAYSPEED_NORMAL); // blocks if no audio is available, but leaves critical section before doing so 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 be dropped. Don't let audio move ahead of the current time thou //we need to be able to start playing at any time //when playing backwords, we try to keep as small buffers as possible // set the time at this 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 ) { // we have succesfully decoded an audio frame, openup the audio device if not already done if (!m_bInitializedOutputDevice) { m_bInitializedOutputDevice = InitializeOutputDevice(); } //Add any packets play m_dvdAudio.AddPackets(audioframe.data, audioframe.size); // store the delay for this pts value so we can calculate the current playing //AddPTSQueue(audioframe.pts, m_dvdAudio.GetDelay() - audioframe.duration);//BRENT } // if we where asked to resync on this packet, do so 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 be calculated after packets have been added as m_audioClock points to the //time after they have been played const __int64 iCurrDiff = (m_audioClock - m_dvdAudio.GetDelay()) - m_pClock->GetClock(); const __int64 iAvDiff = (iClockDiff + iCurrDiff)/2; //Check for discontinuity in the stream, use a moving average to //eliminate highfreq fluctuations of large packet sizes if( ABS(iAvDiff) > 5000 ) // sync clock if average diff is bigger than 5 msec { //Wait untill only the new audio frame wich triggered the discontinuity is 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 one audio frame and returns its uncompressed size int CDVDPlayerAudio::DecodeFrame(DVDAudioFrame &audioframe, bool bDropPacket) { CDVDDemux::DemuxPacket* pPacket = pAudioPacket; int n=48000*2*16/8, len; //Store amount left at this point, and what last pts was unsigned __int64 first_pkt_pts = 0; int first_pkt_size = 0; int first_pkt_used = 0; int result = 0; // make sure the sent frame is 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; } for (;;) { /* NOTE: the 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, we skip the frame */ audio_pkt_size=0; m_pAudioCodec->Reset(); break; } // fix for fucked up decoders //FIXME BRENT if( len > audio_pkt_size ) { CLog::Log(LOGERROR, "CDVDPlayerAudio:DecodeFrame - Codec tried to consume more data than available. Potential memory corruption"); audio_pkt_size=0; m_pAudioCodec->Reset(); assert(0); } // get decoded data and the size of it 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 will result in 0, and that will result in a nice devide exception audioframe.duration = (unsigned int)(((__int64)audioframe.size * DVD_TIME_BASE) / n); // increase audioclock to after the packet m_audioClock += audioframe.duration; } //If we are asked to drop this packet, return a size of zero. then it won't be played //we currently still decode the audio.. this is needed since we still need to know it's //duration to make sure clock is updated correctly. if( bDropPacket ) { result |= DECODE_FLAG_DROP; } return result; } // free the current packet if (pPacket) { CDVDDemuxUtils::FreeDemuxPacket(pPacket); //BRENT FIXME pPacket = NULL; pAudioPacket = NULL; } if (m_messageQueue.RecievedAbortRequest()) return DECODE_FLAG_ABORT; // read next packet and return -1 on error LeaveCriticalSection(&m_critCodecSection); //Leave here as this might stall a 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 is not used here, free if // msg itself will still be available pMsg->Release(); } // if update the audio clock with the pts if (pMsg->IsType(CDVDMsg::DEMUXER_PACKET) || pMsg->IsType(CDVDMsg::GENERAL_RESYNC)) { if (pMsg->IsType(CDVDMsg::GENERAL_RESYNC)) { //player asked us to sync on this package CDVDMsgGeneralResync* pMsgGeneralResync = (CDVDMsgGeneralResync*)pMsg; result |= DECODE_FLAG_RESYNC; m_audioClock = pMsgGeneralResync->GetPts(); } else if (pPacket->pts != DVD_NOPTS_VALUE) // CDVDMsg::DEMUXER_PACKET, pPacket is already set above { if (first_pkt_size == 0) { //first package m_audioClock = pPacket->pts; } else if (first_pkt_pts > pPacket->pts) { //okey first packet in this continous stream, make sure we use the 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 from current packet, untill we find a better value for it. //Should be ok after a couple of frames, as soon as it starts clean on a packet m_audioClock = pPacket->pts; } else if(first_pkt_size == first_pkt_used) { //Nice starting up freshly on the start of a packet, use pts from it 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 to create audio device, (iChannels == 0 || iSampleRate == 0 || iBitsPerSample == 0)"); return false; } CLog::Log(LOGNOTICE, "Creating audio device with codec id: %i, channels: %i, sample rate: %i", m_codec, iChannels, iSampleRate); if (m_dvdAudio.Create(iChannels, iSampleRate, iBitsPerSample, /*bPasstrough*/0)) // always 16 bit with ffmpeg ? //BRENT Passthrough needed? { return true; } CLog::Log(LOGERROR, "Failed Creating audio device with codec id: %i, channels: %i, sample rate: %i", m_codec, iChannels, iSampleRate); return false; }

0 个答案:

没有答案