2017-01-09 219 views
5

我有一個解決方案,我使用Media Foundation的h264編碼器對來自網絡攝像頭的視頻(YUY2)樣本進行編碼。然後,我通過TCP連接將其發送到另一個應用程序,該應用程序使用Media Foundation的h264解碼器將該流解碼爲YUY2格式。解碼後,使用DirectX在屏幕上顯示視頻樣本/圖像。Media Foundation攝像頭視頻H264編碼/解碼在播放時產生僞影

問題是關鍵幀之間的視頻圖像越來越多的文物。當收到關鍵幀時,工件消失。

我將TCP連接從範圍中刪除,並在編碼之後立即進行解碼,但仍然有令人困擾的文物。

下面是從攝像頭接收樣品回調方法:

//------------------------------------------------------------------- 
// OnReadSample 
// 
// Called when the IMFMediaSource::ReadSample method completes. 
//------------------------------------------------------------------- 

HRESULT CPreview::OnReadSample(
    HRESULT hrStatus, 
    DWORD /* dwStreamIndex */, 
    DWORD dwStreamFlags, 
    LONGLONG llTimestamp, 
    IMFSample *pSample  // Can be NULL 
    ) 
{ 
    HRESULT hr = S_OK; 
    IMFMediaBuffer *pBuffer = NULL; 

    EnterCriticalSection(&m_critsec); 

    if (FAILED(hrStatus)) 
    { 
     hr = hrStatus; 
    } 

    if (SUCCEEDED(hr)) 
    { 
     if (pSample) 
     { 
      IMFSample *pEncodedSample = NULL; 
      hr = m_pCodec->EncodeSample(pSample, &pEncodedSample); 
      if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT || pEncodedSample == NULL) 
      { 
       hr = m_pReader->ReadSample((DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM, 0, NULL, NULL, NULL, NULL); 
       LeaveCriticalSection(&m_critsec); 
       return S_OK; 
      } 

      LONGLONG llEncodedSampleTimeStamp = 0; 
      LONGLONG llEncodedSampleDuration = 0; 
      pEncodedSample->GetSampleTime(&llEncodedSampleTimeStamp); 
      pEncodedSample->GetSampleDuration(&llEncodedSampleDuration); 

      pBuffer = NULL; 
      hr = pEncodedSample->GetBufferByIndex(0, &pBuffer); 
      if (hr != S_OK) 
      { 
       hr = m_pReader->ReadSample((DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM, 0, NULL, NULL, NULL, NULL); 
       LeaveCriticalSection(&m_critsec); 
       return hr; 
      } 

      BYTE *pOutBuffer = NULL; 
      DWORD dwMaxLength, dwCurrentLength; 
      hr = pBuffer->Lock(&pOutBuffer, &dwMaxLength, &dwCurrentLength); 
      if (hr != S_OK) 
      { 
       hr = m_pReader->ReadSample((DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM, 0, NULL, NULL, NULL, NULL); 
       LeaveCriticalSection(&m_critsec); 
       return hr; 
      } 
      // Send encoded webcam data to connected clients 
      //SendData(pOutBuffer, dwCurrentLength, llEncodedSampleTimeStamp, llEncodedSampleDuration); 

      pBuffer->Unlock(); 
      SafeRelease(&pBuffer); 

      IMFSample *pDecodedSample = NULL;   
      m_pCodec->DecodeSample(pEncodedSample, &pDecodedSample); 
      if (pDecodedSample != NULL) 
      { 
       pDecodedSample->SetSampleTime(llTimestamp); 
       pDecodedSample->SetSampleTime(llTimestamp - llLastSampleTimeStamp); 
       llLastSampleTimeStamp = llTimestamp; 
       hr = pDecodedSample->GetBufferByIndex(0, &pBuffer); 
       //hr = pSample->GetBufferByIndex(0, &pBuffer); 

       // Draw the frame. 
       if (SUCCEEDED(hr)) 
       { 
        hr = m_draw.DrawFrame(pBuffer); 
       } 
       SafeRelease(&pDecodedSample); 
      } 

      SafeRelease(&pBuffer); 
      SafeRelease(&pEncodedSample);   
     } 
    } 

    // Request the next frame. 
    if (SUCCEEDED(hr)) 
    { 
     hr = m_pReader->ReadSample(
      (DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM, 
      0, 
      NULL, // actual 
      NULL, // flags 
      NULL, // timestamp 
      NULL // sample 
      ); 
    } 

    if (FAILED(hr)) 
    { 
     NotifyError(hr); 
    } 
    SafeRelease(&pBuffer); 

    LeaveCriticalSection(&m_critsec); 
    return hr; 
} 

而這裏的編碼器/解碼器的初始化代碼:

HRESULT Codec::InitializeEncoder() 
    { 
     IMFMediaType *pMFTInputMediaType = NULL, *pMFTOutputMediaType = NULL; 
     IUnknown *spTransformUnk = NULL;  
     DWORD mftStatus = 0; 
     UINT8 blob[] = { 0x00, 0x00, 0x00, 0x01, 0x67, 0x42, 0xc0, 0x1e, 0x96, 0x54, 0x05, 0x01, 
      0xe9, 0x80, 0x80, 0x40, 0x00, 0x00, 0x00, 0x01, 0x68, 0xce, 0x3c, 0x80 }; 

     CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE); 
     MFStartup(MF_VERSION); 

     // Create H.264 encoder. 
     CHECK_HR(CoCreateInstance(CLSID_CMSH264EncoderMFT, NULL, CLSCTX_INPROC_SERVER, IID_IUnknown, (void**)&spTransformUnk), "Failed to create H264 encoder MFT.\n"); 

     CHECK_HR(spTransformUnk->QueryInterface(IID_PPV_ARGS(&pEncoderTransform)), "Failed to get IMFTransform interface from H264 encoder MFT object.\n"); 

     // Transform output type 
     MFCreateMediaType(&pMFTOutputMediaType); 
     pMFTOutputMediaType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video); 
     pMFTOutputMediaType->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_H264); 
     pMFTOutputMediaType->SetUINT32(MF_MT_AVG_BITRATE, 500000); 
     CHECK_HR(MFSetAttributeSize(pMFTOutputMediaType, MF_MT_FRAME_SIZE, 640, 480), "Failed to set frame size on H264 MFT out type.\n"); 
     CHECK_HR(MFSetAttributeRatio(pMFTOutputMediaType, MF_MT_FRAME_RATE, 30, 1), "Failed to set frame rate on H264 MFT out type.\n"); 
     CHECK_HR(MFSetAttributeRatio(pMFTOutputMediaType, MF_MT_PIXEL_ASPECT_RATIO, 1, 1), "Failed to set aspect ratio on H264 MFT out type.\n"); 
     pMFTOutputMediaType->SetUINT32(MF_MT_INTERLACE_MODE, MFVideoInterlace_MixedInterlaceOrProgressive); 
     pMFTOutputMediaType->SetUINT32(MF_MT_ALL_SAMPLES_INDEPENDENT, TRUE); 

     // Special attributes for H264 transform, if needed 
     /*CHECK_HR(pMFTOutputMediaType->SetUINT32(MF_MT_MPEG2_PROFILE, eAVEncH264VProfile_Base), "Failed to set profile on H264 MFT out type.\n"); 
     CHECK_HR(pMFTOutputMediaType->SetUINT32(MF_MT_MPEG2_LEVEL, eAVEncH264VLevel4), "Failed to set level on H264 MFT out type.\n"); 
     CHECK_HR(pMFTOutputMediaType->SetUINT32(MF_MT_MAX_KEYFRAME_SPACING, 10), "Failed to set key frame interval on H264 MFT out type.\n"); 
     CHECK_HR(pMFTOutputMediaType->SetUINT32(CODECAPI_AVEncCommonQuality, 100), "Failed to set H264 codec qulaity.\n"); 
     CHECK_HR(pMFTOutputMediaType->SetUINT32(CODECAPI_AVEncMPVGOPSize, 1), "Failed to set CODECAPI_AVEncMPVGOPSize = 1\n");*/ 
     CHECK_HR(pEncoderTransform->SetOutputType(0, pMFTOutputMediaType, 0), "Failed to set output media type on H.264 encoder MFT.\n"); 

     // Transform input type 
     MFCreateMediaType(&pMFTInputMediaType); 
     pMFTInputMediaType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video); 
     pMFTInputMediaType->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_YUY2); 
     CHECK_HR(MFSetAttributeSize(pMFTInputMediaType, MF_MT_FRAME_SIZE, 640, 480), "Failed to set frame size on H264 MFT out type.\n"); 
     CHECK_HR(MFSetAttributeRatio(pMFTInputMediaType, MF_MT_FRAME_RATE, 30, 1), "Failed to set frame rate on H264 MFT out type.\n"); 
     CHECK_HR(MFSetAttributeRatio(pMFTInputMediaType, MF_MT_PIXEL_ASPECT_RATIO, 1, 1), "Failed to set aspect ratio on H264 MFT out type.\n"); 
     CHECK_HR(pEncoderTransform->SetInputType(0, pMFTInputMediaType, 0), "Failed to set input media type on H.264 encoder MFT.\n"); 

     CHECK_HR(pEncoderTransform->GetInputStatus(0, &mftStatus), "Failed to get input status from H.264 MFT.\n"); 
     if (MFT_INPUT_STATUS_ACCEPT_DATA != mftStatus) 
     { 
      printf("E: pEncoderTransform->GetInputStatus() not accept data.\n"); 
      goto done; 
     } 

     CHECK_HR(pEncoderTransform->ProcessMessage(MFT_MESSAGE_COMMAND_FLUSH, NULL), "Failed to process FLUSH command on H.264 MFT.\n"); 
     CHECK_HR(pEncoderTransform->ProcessMessage(MFT_MESSAGE_NOTIFY_BEGIN_STREAMING, NULL), "Failed to process BEGIN_STREAMING command on H.264 MFT.\n"); 
     CHECK_HR(pEncoderTransform->ProcessMessage(MFT_MESSAGE_NOTIFY_START_OF_STREAM, NULL), "Failed to process START_OF_STREAM command on H.264 MFT.\n"); 

     return S_OK; 

    done: 

     SafeRelease(&pMFTInputMediaType); 
     SafeRelease(&pMFTOutputMediaType); 

     return S_FALSE; 
    } 

    HRESULT Codec::InitializeDecoder() 
    { 
     IUnknown *spTransformUnk = NULL; 
     IMFMediaType *pMFTOutputMediaType = NULL; 
     IMFMediaType *pMFTInputMediaType = NULL; 
     DWORD mftStatus = 0; 

     // Create H.264 decoder. 
     CHECK_HR(CoCreateInstance(CLSID_CMSH264DecoderMFT, NULL, CLSCTX_INPROC_SERVER, IID_IUnknown, (void**)&spTransformUnk), "Failed to create H264 decoder MFT.\n"); 

     // Query for the IMFTransform interface 
     CHECK_HR(spTransformUnk->QueryInterface(IID_PPV_ARGS(&pDecoderTransform)), "Failed to get IMFTransform interface from H264 decoder MFT object.\n"); 

     // Create input mediatype for the decoder 
     MFCreateMediaType(&pMFTInputMediaType); 
     pMFTInputMediaType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video); 
     pMFTInputMediaType->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_H264); 
     CHECK_HR(MFSetAttributeSize(pMFTInputMediaType, MF_MT_FRAME_SIZE, 640, 480), "Failed to set frame size on H264 MFT out type.\n"); 
     CHECK_HR(MFSetAttributeRatio(pMFTInputMediaType, MF_MT_FRAME_RATE, 30, 1), "Failed to set frame rate on H264 MFT out type.\n"); 
     CHECK_HR(MFSetAttributeRatio(pMFTInputMediaType, MF_MT_PIXEL_ASPECT_RATIO, 1, 1), "Failed to set aspect ratio on H264 MFT out type.\n"); 
     pMFTInputMediaType->SetUINT32(MF_MT_INTERLACE_MODE, MFVideoInterlace_MixedInterlaceOrProgressive); 
     pMFTInputMediaType->SetUINT32(MF_MT_ALL_SAMPLES_INDEPENDENT, TRUE); 
     CHECK_HR(pDecoderTransform->SetInputType(0, pMFTInputMediaType, 0), "Failed to set input media type on H.264 encoder MFT.\n"); 

     CHECK_HR(pDecoderTransform->GetInputStatus(0, &mftStatus), "Failed to get input status from H.264 MFT.\n"); 
     if (MFT_INPUT_STATUS_ACCEPT_DATA != mftStatus) 
     { 
      printf("E: pDecoderTransform->GetInputStatus() not accept data.\n"); 
      goto done; 
     } 

     // Create outmedia type for the decoder 
     MFCreateMediaType(&pMFTOutputMediaType); 
     pMFTOutputMediaType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video); 
     pMFTOutputMediaType->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_YUY2); 
     CHECK_HR(MFSetAttributeSize(pMFTOutputMediaType, MF_MT_FRAME_SIZE, 640, 480), "Failed to set frame size on H264 MFT out type.\n"); 
     CHECK_HR(MFSetAttributeRatio(pMFTOutputMediaType, MF_MT_FRAME_RATE, 30, 1), "Failed to set frame rate on H264 MFT out type.\n"); 
     CHECK_HR(MFSetAttributeRatio(pMFTOutputMediaType, MF_MT_PIXEL_ASPECT_RATIO, 1, 1), "Failed to set aspect ratio on H264 MFT out type.\n"); 
     CHECK_HR(pDecoderTransform->SetOutputType(0, pMFTOutputMediaType, 0), "Failed to set output media type on H.264 decoder MFT.\n"); 

     CHECK_HR(pDecoderTransform->ProcessMessage(MFT_MESSAGE_COMMAND_FLUSH, NULL), "Failed to process FLUSH command on H.264 MFT.\n"); 
     CHECK_HR(pDecoderTransform->ProcessMessage(MFT_MESSAGE_NOTIFY_BEGIN_STREAMING, NULL), "Failed to process BEGIN_STREAMING command on H.264 MFT.\n"); 
     CHECK_HR(pDecoderTransform->ProcessMessage(MFT_MESSAGE_NOTIFY_START_OF_STREAM, NULL), "Failed to process START_OF_STREAM command on H.264 MFT.\n"); 

     return S_OK; 

    done: 

     SafeRelease(&pMFTInputMediaType); 
     SafeRelease(&pMFTOutputMediaType); 

     return S_FALSE; 
    } 

下面是實際的解碼/編碼器部分:

HRESULT Codec::EncodeSample(IMFSample *pSample, IMFSample **ppEncodedSample) 
{ 
    return TransformSample(pEncoderTransform, pSample, ppEncodedSample); 
} 

HRESULT Codec::DecodeSample(IMFSample *pSample, IMFSample **ppEncodedSample) 
{ 
    return TransformSample(pDecoderTransform, pSample, ppEncodedSample); 
} 

HRESULT Codec::TransformSample(IMFTransform *pTransform, IMFSample *pSample, IMFSample **ppSampleOut) 
{ 
    IMFSample *pOutSample = NULL; 
    IMFMediaBuffer *pBuffer = NULL; 
    DWORD mftOutFlags; 
    pTransform->ProcessInput(0, pSample, 0); 
    CHECK_HR(pTransform->GetOutputStatus(&mftOutFlags), "H264 MFT GetOutputStatus failed.\n"); 

    // Note: Decoder does not return MFT flag MFT_OUTPUT_STATUS_SAMPLE_READY, so we just need to rely on S_OK return 
    if (pTransform == pEncoderTransform && mftOutFlags == S_OK) 
    { 
     return S_OK; 
    } 
    else if (pTransform == pEncoderTransform && mftOutFlags == MFT_OUTPUT_STATUS_SAMPLE_READY || 
     pTransform == pDecoderTransform && mftOutFlags == S_OK) 
    { 
     DWORD processOutputStatus = 0; 
     MFT_OUTPUT_DATA_BUFFER outputDataBuffer; 
     MFT_OUTPUT_STREAM_INFO StreamInfo; 
     pTransform->GetOutputStreamInfo(0, &StreamInfo); 

     CHECK_HR(MFCreateSample(&pOutSample), "Failed to create MF sample.\n"); 
     CHECK_HR(MFCreateMemoryBuffer(StreamInfo.cbSize, &pBuffer), "Failed to create memory buffer.\n"); 
     if (pTransform == pEncoderTransform) 
      CHECK_HR(pBuffer->SetCurrentLength(StreamInfo.cbSize), "Failed SetCurrentLength.\n"); 
     CHECK_HR(pOutSample->AddBuffer(pBuffer), "Failed to add sample to buffer.\n");  
     outputDataBuffer.dwStreamID = 0; 
     outputDataBuffer.dwStatus = 0; 
     outputDataBuffer.pEvents = NULL; 
     outputDataBuffer.pSample = pOutSample; 

     HRESULT hr = pTransform->ProcessOutput(0, 1, &outputDataBuffer, &processOutputStatus); 
     if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) 
     { 
      SafeRelease(&pBuffer); 
      SafeRelease(&pOutSample); 
      return hr; 
     } 

     LONGLONG llVideoTimeStamp, llSampleDuration; 
     pSample->GetSampleTime(&llVideoTimeStamp); 
     pSample->GetSampleDuration(&llSampleDuration); 
     CHECK_HR(outputDataBuffer.pSample->SetSampleTime(llVideoTimeStamp), "Error setting MFT sample time.\n"); 
     CHECK_HR(outputDataBuffer.pSample->SetSampleDuration(llSampleDuration), "Error setting MFT sample duration.\n");   
     if (pTransform == pEncoderTransform) 
     { 
      IMFMediaBuffer *pMediaBuffer = NULL; 
      DWORD dwBufLength; 
      CHECK_HR(pOutSample->ConvertToContiguousBuffer(&pMediaBuffer), "ConvertToContiguousBuffer failed.\n"); 
      CHECK_HR(pMediaBuffer->GetCurrentLength(&dwBufLength), "Get buffer length failed.\n"); 

      WCHAR *strDebug = new WCHAR[256]; 
      wsprintf(strDebug, L"Encoded sample ready: time %I64d, sample duration %I64d, sample size %i.\n", llVideoTimeStamp, llSampleDuration, dwBufLength); 
      OutputDebugString(strDebug); 
      SafeRelease(&pMediaBuffer); 
     } 
     else if (pTransform == pDecoderTransform) 
     { 
      IMFMediaBuffer *pMediaBuffer = NULL; 
      DWORD dwBufLength; 
      CHECK_HR(pOutSample->ConvertToContiguousBuffer(&pMediaBuffer), "ConvertToContiguousBuffer failed.\n"); 
      CHECK_HR(pMediaBuffer->GetCurrentLength(&dwBufLength), "Get buffer length failed.\n"); 

      WCHAR *strDebug = new WCHAR[256]; 
      wsprintf(strDebug, L"Decoded sample ready: time %I64d, sample duration %I64d, sample size %i.\n", llVideoTimeStamp, llSampleDuration, dwBufLength); 
      OutputDebugString(strDebug); 
      SafeRelease(&pMediaBuffer); 
     } 

     // Decoded sample out 
     *ppSampleOut = pOutSample; 

     //SafeRelease(&pMediaBuffer); 
     SafeRelease(&pBuffer); 

     return S_OK; 
    } 

done: 
    SafeRelease(&pBuffer); 
    SafeRelease(&pOutSample); 

    return S_FALSE; 
} 

我已經爲此搜索了很長一段時間的解決方案,並發現一個闕這個定義與我的問題非常相似,但由於它是針對不同的API,所以對我來說沒有任何幫助。 FFMPEG decoding artifacts between keyframes

最好的問候, 託尼Riikonen

+0

我注意到,如果在流開始後等待大約30-60秒,那麼工件消失了。這可能是一些緩衝問題,或者我應該在讓解碼器掌握它們之前緩衝一下樣本。 或者我的時間戳有問題嗎? –

+1

pMFTOutputMediaType-> SetUINT32(MF_MT_AVG_BITRATE,500000); 500kbps對比特率來說太低。編碼質量在這種情況下會很糟糕,並且會導致僞像。嘗試5000000(5Mbps),而不是更高的價值。 – VuVirt

+0

已嘗試較大的比特率,工件的大小變小,但不會消失。我不是在談論有損編碼算法造成的常見僞影,而是在我看來,像是丟失數據或樣本不合格的僞影。 是否有可能在OnReadSample回調方法中,因爲我需要第一個編碼樣本的多個輸入樣本,傳遞給此回調函數的IMFSamples被釋放?我想可能是提供樣本的副本是回調函數,然後在完成它們之後釋放它們。 –

回答

1

這聽起來像質量/比特率的問題。

pMFTOutputMediaType->SetUINT32(MF_MT_AVG_BITRATE, 500000); 

500kbps對比特率來說太低了,你可以試試更大的比如5,10或20Mbps。

我可以建議:

  1. 既然你正在創建的H264編碼器爲你自己,你可以查詢它ICodecAPI和嘗試不同的設置。即,CODECAPI_AVEncCommonRateControlMode,CODECAPI_AVEncCommonQuality,CODECAPI_AVEncAdaptiveMode,CODECAPI_AVEncCommonQualityVsSpeed,CODECAPI_AVEncVideoEncodeQP。

  2. 您也可以嘗試創建一個硬件H264編碼器和它使用IMFDXGIDeviceManager(Windows 8和上面?)

0

這聽起來像一個IP(B)幀順序問題。

編碼幀順序與解碼幀順序不同。我沒有測試你的代碼,但我認爲編碼器按照編碼順序提供了幀,並且你需要在渲染之前重新排序幀。

+0

似乎即使使用Microsoft API示例並將其另存爲MP4,工件仍然存在。所以我在猜測,微軟的H264編碼器/解碼器在處理網絡攝像頭圖像上的噪聲方面是不是很好(因爲大多數便宜的網絡攝像頭會產生噪聲圖像),或者編碼器/解碼器有些破損。 我決定改用WMV3代替。不確定哪個答案是正確答案,因爲實際問題沒有完全解決,並且可能是開發者手中沒有的問題。 –

0

這個問題似乎有答案,但我仍然想分享我的經驗。希望能幫助遇到類似問題的人。

在解碼H264時,我也遇到了類似的工件問題。但是,就我而言,該流來自視頻捕獲設備,並且工件在流開始後30-60秒後不會消失。

在我看來,我認爲具有正常設置的解碼器由於低延遲而無法解碼實時流。因此,我嘗試啓用CODECAPI_AVLowLatencyMode,它可以將解碼/編碼模式設置爲低延遲以實現實時通信或實時捕獲。 (要了解更多詳情,請參考MS https://msdn.microsoft.com/zh-tw/library/windows/desktop/hh447590(v=vs.85).aspx 的以下鏈接)幸運的是,問題已經解決,解碼器正常工作。

儘管我們的問題有點不同,但您可以嘗試在您的情況下啓用/禁用CODECAPI_AVLowLatencyMode,並且我希望您也可以獲得好消息。