2017-10-12 3 views
0

J'ai créé une bibliothèque C++ gérée pour mon projet C# pour encoder des images et du son dans une base mp4 sur le tutoriel MSDN SinkWriter. Pour tester si le résultat est correct, j'ai créé une méthode qui fournit 600 images. Ces images représentent une vidéo de 10 secondes avec 60 images par seconde.La durée de lecture du fichier MF SinkWriter mp4 est moitié moins de temps lors de l'ajout d'un échantillon audio la vitesse de lecture des images est deux fois plus rapide

Les images que je fournissent le changement à chaque seconde et mon fichier audio contient une voix qui compte à 10.

Le problème que je suis face est que la sortie vidéo est actuellement seulement 5 secondes. Les méta-données de la vidéo montrent que c'est 10 secondes mais ne l'est pas. Aussi la voix compte à peine jusqu'à 5.

Si j'écris seulement les échantillons d'image sans partie audio la durée de la vidéo est les 10 secondes attendues.

Qu'est-ce qui me manque ici?

Voici quelques parties de mon application.

Ceci est la partie C# que j'utilise pour créer les 600 images, puis j'appelle la méthode PushFrame également dans la partie C#.

var videoFrameCount = 10 * FPS; 
SetBinaryImage(); 

for (int i = 0; i <= videoFrameCount; i++) 
{ 
    // New picture every second 
    if (i > 0 && i % FPS == 0) 
    { 
     SetBinaryImage(); 
    } 

    PushFrame(); 
} 

Les copies de procédé PushFrame l'image et de données audio vers le pointeur fourni par le SinkWriter. Ensuite, j'appelle la méthode PushFrame du SinkWriter.

private void PushFrame() 
{ 
    try 
    { 
     encodeStopwatch.Reset(); 
     encodeStopwatch.Start(); 

     // Video 
     var frameBufferHandler = GCHandle.Alloc(frameBuffer, GCHandleType.Pinned); 
     frameBufferPtr = frameBufferHandler.AddrOfPinnedObject(); 
     CopyImageDataToPointer(BinaryImage, ScreenWidth, ScreenHeight, frameBufferPtr); 

     // Audio 
     var audioBufferHandler = GCHandle.Alloc(audioBuffer, GCHandleType.Pinned); 
     audioBufferPtr = audioBufferHandler.AddrOfPinnedObject(); 
     var readLength = audioBuffer.Length; 

     if (BinaryAudio.Length - (audioOffset + audioBuffer.Length) < 0) 
     { 
      readLength = BinaryAudio.Length - audioOffset; 
     } 

     if (!EndOfFile) 
     { 
      Marshal.Copy(BinaryAudio, audioOffset, (IntPtr)audioBufferPtr, readLength); 
      audioOffset += audioBuffer.Length; 

     } 

     if (readLength < audioBuffer.Length && !EndOfFile) 
     { 
      EndOfFile = true; 
     } 

     unsafe 
     { 
      // Copy video data 
      var yuv = SinkWriter.VideoCapturerBuffer(); 
      SinkWriter.Encode((byte*)frameBufferPtr, ScreenWidth, ScreenHeight, (int)SWPF.SWPF_RGB, yuv); 

      // Copy audio data 
      var audioDestPtr = SinkWriter.AudioCapturerBuffer(); 
      SinkWriter.EncodeAudio((byte*)audioBufferPtr, audioDestPtr); 

      SinkWriter.PushFrame(); 
     } 

     encodeStopwatch.Stop(); 
     Console.WriteLine($"YUV frame generated in: {encodeStopwatch.TakeTotalMilliseconds()} ms"); 
    } 
    catch (Exception ex) 
    { 
    } 
} 

Voici quelques parties que j'ai ajoutées au SinkWriter en C++. Les MediaTypes pour la partie audio sont ok je suppose que parce que la lecture de l'audio fonctionne.

Le rtStart et rtDuration sont définis comme suit:

LONGLONG rtStart = 0; 
UINT64 rtDuration; 
MFFrameRateToAverageTimePerFrame(fps, 1, &rtDuration); 

Les deux tampons de codeurs sont utilisés comme ceci

int SinkWriter::Encode(Byte * rgbBuf, int w, int h, int pxFormat, Byte * yufBuf) 
{ 
    const LONG cbWidth = 4 * VIDEO_WIDTH; 
    const DWORD cbBuffer = cbWidth * VIDEO_HEIGHT; 

    // Create a new memory buffer. 
    HRESULT hr = MFCreateMemoryBuffer(cbBuffer, &pFrameBuffer); 

    // Lock the buffer and copy the video frame to the buffer. 
    if (SUCCEEDED(hr)) 
    { 
     hr = pFrameBuffer->Lock(&yufBuf, NULL, NULL); 
    } 

    if (SUCCEEDED(hr)) 
    { 
     // Calculate the stride 
     DWORD bitsPerPixel = GetBitsPerPixel(pxFormat); 
     DWORD bytesPerPixel = bitsPerPixel/8; 
     DWORD stride = w * bytesPerPixel; 

     // Copy image in yuv pointer 
     hr = MFCopyImage(
      yufBuf,      // Destination buffer. 
      stride,     // Destination stride. 
      rgbBuf,  // First row in source image. 
      stride,     // Source stride. 
      stride,     // Image width in bytes. 
      h    // Image height in pixels. 
     ); 
    } 

    if (pFrameBuffer) 
    { 
     pFrameBuffer->Unlock(); 
    } 

    // Set the data length of the buffer. 
    if (SUCCEEDED(hr)) 
    { 
     hr = pFrameBuffer->SetCurrentLength(cbBuffer); 
    } 

    if (SUCCEEDED(hr)) 
    { 
     return 0; 
    } 
    else 
    { 
     return -1; 
    } 

    return 0; 
} 

int SinkWriter::EncodeAudio(Byte * src, Byte * dest) 
{ 
    DWORD samplePerSecond = AUDIO_SAMPLES_PER_SECOND * AUDIO_BITS_PER_SAMPLE * AUDIO_NUM_CHANNELS; 
    DWORD cbBuffer = samplePerSecond/1000; 

    // Create a new memory buffer. 
    HRESULT hr = MFCreateMemoryBuffer(cbBuffer, &pAudioBuffer); 

    // Lock the buffer and copy the video frame to the buffer. 
    if (SUCCEEDED(hr)) 
    { 
     hr = pAudioBuffer->Lock(&dest, NULL, NULL); 
    } 

    CopyMemory(dest, src, cbBuffer); 

    if (pAudioBuffer) 
    { 
     pAudioBuffer->Unlock(); 
    } 

    // Set the data length of the buffer. 
    if (SUCCEEDED(hr)) 
    { 
     hr = pAudioBuffer->SetCurrentLength(cbBuffer); 
    } 

    if (SUCCEEDED(hr)) 
    { 
     return 0; 
    } 
    else 
    { 
     return -1; 
    } 

    return 0; 
} 

Ceci est la méthode PushFrame du SinkWriter qui transmet la SinkWriter, streamIndex, audioIndex, rtStart et rtDuration à la méthode WriteFrame.

int SinkWriter::PushFrame() 
{ 
    if (initialized) 
    { 
     HRESULT hr = WriteFrame(ptrSinkWriter, stream, audio, rtStart, rtDuration); 
     if (FAILED(hr)) 
     { 
      return -1; 
     } 

     rtStart += rtDuration; 

     return 0; 
    } 

    return -1; 
} 

Et voici la méthode WriteFrame qui combine l'échantillon vidéo et audio.

HRESULT SinkWriter::WriteFrame(IMFSinkWriter *pWriter, DWORD streamIndex, DWORD audioStreamIndex, const LONGLONG& rtStart, const LONGLONG& rtDuration) 
{ 
    IMFSample *pVideoSample = NULL; 

    // Create a media sample and add the buffer to the sample. 
    HRESULT hr = MFCreateSample(&pVideoSample); 

    if (SUCCEEDED(hr)) 
    { 
     hr = pVideoSample->AddBuffer(pFrameBuffer); 
    } 
    if (SUCCEEDED(hr)) 
    { 
     pVideoSample->SetUINT32(MFSampleExtension_Discontinuity, FALSE); 
    } 
    // Set the time stamp and the duration. 
    if (SUCCEEDED(hr)) 
    { 
     hr = pVideoSample->SetSampleTime(rtStart); 
    } 
    if (SUCCEEDED(hr)) 
    { 
     hr = pVideoSample->SetSampleDuration(rtDuration); 
    } 

    // Send the sample to the Sink Writer. 
    if (SUCCEEDED(hr)) 
    { 
     hr = pWriter->WriteSample(streamIndex, pVideoSample); 
    } 

    // Audio 
    IMFSample *pAudioSample = NULL; 

    if (SUCCEEDED(hr)) 
    { 
     hr = MFCreateSample(&pAudioSample); 
    } 

    if (SUCCEEDED(hr)) 
    { 
     hr = pAudioSample->AddBuffer(pAudioBuffer); 
    } 

    // Set the time stamp and the duration. 
    if (SUCCEEDED(hr)) 
    { 
     hr = pAudioSample->SetSampleTime(rtStart); 
    } 
    if (SUCCEEDED(hr)) 
    { 
     hr = pAudioSample->SetSampleDuration(rtDuration); 
    } 
    // Send the sample to the Sink Writer. 
    if (SUCCEEDED(hr)) 
    { 
     hr = pWriter->WriteSample(audioStreamIndex, pAudioSample); 
    } 


    SafeRelease(&pVideoSample); 
    SafeRelease(&pFrameBuffer); 
    SafeRelease(&pAudioSample); 
    SafeRelease(&pAudioBuffer); 
    return hr; 
} 
+0

'pVideoSample-> SetSampleTime' arguments sont probablement la moitié de ce que vous voulez. À tout le moins, vous devriez vérifier cela avec le débogueur et l'exclure. –

+0

Le 'SetSampleTime' devrait être ok parce que quand je supprime l'AudioSample la durée et l'heure de la vidéo sont valides. Mais je vais essayer de doubler le temps. Editer: J'ai essayé de doubler mais la création de la vidéo ne fonctionnera plus. Après les trames severel, je prends des âges pour générer les images restantes. – datoml

Répondre

0

Le problème était que le calcul de la taille de la mémoire tampon pour l'audio était incorrect. Ce est le calcul droite:

var avgBytesPerSecond = sampleRate * 2 * channels; 
var avgBytesPerMillisecond = avgBytesPerSecond/1000; 
var bufferSize = avgBytesPerMillisecond * (1000/60); 
audioBuffer = new byte[bufferSize]; 

Dans ma question, j'ai eu la taille de la mémoire tampon pour une milliseconde. Il semble donc que le cadre MF accélère les images afin que l'audio sonne bien. Après avoir fixé la taille de la mémoire tampon, la vidéo a exactement la durée que je m'attendais et le son n'a pas non plus d'erreurs.