FFMPEG Plugin: Implement video input buffer padding.

- Padding is required by FFMPEG for correct operation of all video decoders.
  FFMPEG performs some speed optimizations under the hood that may lead to
  reading over the end of the chunk buffer wouldn't there have been padding
  applied.
- Note: Padding is required for audio decoders, too. I will tackle this in some
  later commits. For the time being we have a degradation in code reuse, due to
  different memory ownership of chunk buffers in audio and video decoder path.
  Audio path must not care about freeing chunk buffers whereas video path must.
- Fix coding style and some typos.
- Update documentation accordingly.
This commit is contained in:
Colin Günther 2014-08-05 00:04:27 +02:00
parent cb721c5963
commit 2d83b8419c
2 changed files with 75 additions and 8 deletions

View File

@ -106,6 +106,7 @@ AVCodecDecoder::AVCodecDecoder()
fOutputFrameSize(0), fOutputFrameSize(0),
fChunkBuffer(NULL), fChunkBuffer(NULL),
fVideoChunkBuffer(NULL),
fChunkBufferOffset(0), fChunkBufferOffset(0),
fChunkBufferSize(0), fChunkBufferSize(0),
fAudioDecodeError(false), fAudioDecodeError(false),
@ -140,6 +141,9 @@ AVCodecDecoder::~AVCodecDecoder()
if (fCodecInitDone) if (fCodecInitDone)
avcodec_close(fContext); avcodec_close(fContext);
free(fVideoChunkBuffer);
// TODO: Replace with fChunkBuffer, once audio part is
// responsible for freeing the chunk buffer, too.
free(fDecodedData); free(fDecodedData);
av_free(fPostProcessedDecodedPicture); av_free(fPostProcessedDecodedPicture);
@ -260,6 +264,9 @@ AVCodecDecoder::SeekedTo(int64 frame, bigtime_t time)
} }
// Flush internal buffers as well. // Flush internal buffers as well.
free(fVideoChunkBuffer);
// TODO: Replace with fChunkBuffer, once audio part is
// responsible for freeing the chunk buffer, too.
fChunkBuffer = NULL; fChunkBuffer = NULL;
fChunkBufferOffset = 0; fChunkBufferOffset = 0;
fChunkBufferSize = 0; fChunkBufferSize = 0;
@ -721,7 +728,10 @@ AVCodecDecoder::_DecodeVideo(void* outBuffer, int64* outFrameCount,
On first call, the member variables fSwsContext / fFormatConversionFunc On first call, the member variables fSwsContext / fFormatConversionFunc
are initialized. are initialized.
\return B_OK, when we successfully decoded one video frame \returns B_OK when we successfully decoded one video frame
\returns B_LAST_BUFFER_ERROR when there are no more video frames available.
\returns B_NO_MEMORY when we have no memory left for correct operation.
\returns Other Errors
*/ */
status_t status_t
AVCodecDecoder::_DecodeNextVideoFrame() AVCodecDecoder::_DecodeNextVideoFrame()
@ -733,7 +743,8 @@ AVCodecDecoder::_DecodeNextVideoFrame()
if (fTempPacket.size == 0) { if (fTempPacket.size == 0) {
// Our packet buffer is empty, so fill it now. // Our packet buffer is empty, so fill it now.
status_t getNextChunkStatus = GetNextChunk(&fChunkBuffer, const void* chunkBuffer = NULL;
status_t getNextChunkStatus = GetNextChunk(&chunkBuffer,
&fChunkBufferSize, &chunkMediaHeader); &fChunkBufferSize, &chunkMediaHeader);
switch (getNextChunkStatus) { switch (getNextChunkStatus) {
case B_OK: case B_OK:
@ -743,17 +754,31 @@ AVCodecDecoder::_DecodeNextVideoFrame()
return _FlushOneVideoFrameFromDecoderBuffer(); return _FlushOneVideoFrameFromDecoderBuffer();
default: default:
TRACE("AVCodecDecoder::_DecodeNextVideoFrame(): error from " TRACE("AVCodecDecoder::_DecodeNextVideoFrame(): error from"
"GetNextChunk(): %s\n", strerror(err)); " GetNextChunk(): %s\n", strerror(err));
return getNextChunkStatus; return getNextChunkStatus;
} }
fTempPacket.data = static_cast<uint8_t*>(const_cast<void*>( free(fVideoChunkBuffer);
fChunkBuffer)); // Free any previously used chunk buffer first
// TODO: Replace with fChunkBuffer, once audio part is
// responsible for freeing the chunk buffer, too.
fVideoChunkBuffer
= static_cast<uint8_t*>(const_cast<void*>(chunkBuffer));
// TODO: Replace fVideoChunkBuffer with fChunkBuffer, once
// audio part is responsible for freeing the chunk buffer, too.
status_t chunkBufferPaddingStatus
= _AddInputBufferPaddingToVideoChunkBuffer();
if (chunkBufferPaddingStatus != B_OK)
return chunkBufferPaddingStatus;
fTempPacket.data = fVideoChunkBuffer;
// TODO: Replace with fChunkBuffer, once audio part is
// responsible for freeing the chunk buffer, too.
fTempPacket.size = fChunkBufferSize; fTempPacket.size = fChunkBufferSize;
fContext->reordered_opaque = chunkMediaHeader.start_time; fContext->reordered_opaque = chunkMediaHeader.start_time;
// Let ffmpeg handle the relationship between start_time and // Let FFMPEG handle the relationship between start_time and
// decoded video frame. // decoded video frame.
// //
// Explanation: // Explanation:
@ -779,7 +804,7 @@ AVCodecDecoder::_DecodeNextVideoFrame()
#ifdef LOG_STREAM_TO_FILE #ifdef LOG_STREAM_TO_FILE
if (sDumpedPackets < 100) { if (sDumpedPackets < 100) {
sStreamLogFile.Write(fChunkBuffer, fChunkBufferSize); sStreamLogFile.Write(chunkBuffer, fChunkBufferSize);
printf("wrote %ld bytes\n", fChunkBufferSize); printf("wrote %ld bytes\n", fChunkBufferSize);
sDumpedPackets++; sDumpedPackets++;
} else if (sDumpedPackets == 100) } else if (sDumpedPackets == 100)
@ -857,6 +882,39 @@ AVCodecDecoder::_DecodeNextVideoFrame()
} }
/*! \brief Adds a "safety net" of additional memory to fVideoChunkBuffer as
required by FFMPEG for input buffers to video decoders.
This is needed so that some decoders can read safely a predefined number of
bytes at a time for performance optimization purposes.
The additonal memory has a size of FF_INPUT_BUFFER_PADDING_SIZE as defined
in avcodec.h.
\returns B_OK Padding was successful. You are responsible for releasing the
allocated memory.
\returns B_NO_MEMORY Padding failed. The memory in fVideoChunkBuffer is not
freed. NULL is assigned to fVideoChunkBuffer.
*/
status_t
AVCodecDecoder::_AddInputBufferPaddingToVideoChunkBuffer()
{
// TODO: Rename fVideoChunkBuffer to fChunkBuffer, once the audio part is
// responsible for releasing the chunk buffer, too.
fVideoChunkBuffer
= static_cast<uint8_t*>(realloc(static_cast<void*>(fVideoChunkBuffer),
fChunkBufferSize + FF_INPUT_BUFFER_PADDING_SIZE));
if (fVideoChunkBuffer == NULL)
return B_NO_MEMORY;
memset(fVideoChunkBuffer + fChunkBufferSize, 0,
FF_INPUT_BUFFER_PADDING_SIZE);
// Establish safety net, by zero'ing the padding area.
return B_OK;
}
/*! \brief Executes all steps needed for a freshly decoded video frame. /*! \brief Executes all steps needed for a freshly decoded video frame.
\see _UpdateMediaHeaderForVideoFrame() and \see _UpdateMediaHeaderForVideoFrame() and

View File

@ -66,6 +66,10 @@ private:
media_header* mediaHeader, media_header* mediaHeader,
media_decode_info* info); media_decode_info* info);
status_t _DecodeNextVideoFrame(); status_t _DecodeNextVideoFrame();
status_t _AddInputBufferPaddingToVideoChunkBuffer();
// TODO: Remove the "Video" word once
// the audio part is responsible for
// freeing the chunk buffer, too.
void _HandleNewVideoFrameAndUpdateSystemState(); void _HandleNewVideoFrameAndUpdateSystemState();
status_t _FlushOneVideoFrameFromDecoderBuffer(); status_t _FlushOneVideoFrameFromDecoderBuffer();
void _UpdateMediaHeaderForVideoFrame(); void _UpdateMediaHeaderForVideoFrame();
@ -106,6 +110,11 @@ private:
// sample size * channel count // sample size * channel count
const void* fChunkBuffer; const void* fChunkBuffer;
uint8_t* fVideoChunkBuffer;
// TODO: Remove and use fChunkBuffer again
// (with type uint8_t*) once the audio part is
// responsible for freeing the chunk buffer,
// too.
int32 fChunkBufferOffset; int32 fChunkBufferOffset;
size_t fChunkBufferSize; size_t fChunkBufferSize;
bool fAudioDecodeError; bool fAudioDecodeError;