diff options
Diffstat (limited to 'Src/h264/h264_flv_decoder.cpp')
-rw-r--r-- | Src/h264/h264_flv_decoder.cpp | 220 |
1 files changed, 220 insertions, 0 deletions
diff --git a/Src/h264/h264_flv_decoder.cpp b/Src/h264/h264_flv_decoder.cpp new file mode 100644 index 00000000..f9a6f6c7 --- /dev/null +++ b/Src/h264/h264_flv_decoder.cpp @@ -0,0 +1,220 @@ +#include "h264_flv_decoder.h" +#include "../Winamp/wa_ipc.h" // for YV12_PLANES +#include <Mferror.h> + +int FLVDecoderCreator::CreateVideoDecoder(int format_type, int width, int height, ifc_flvvideodecoder **decoder) +{ + if (format_type == FLV::VIDEO_FORMAT_AVC) + { + MFTDecoder *ctx = new MFTDecoder(); + if (!ctx || FAILED(ctx->Open())) { + delete ctx; + return CREATEDECODER_FAILURE; + } + *decoder = new FLVH264(ctx); + return CREATEDECODER_SUCCESS; + } + return CREATEDECODER_NOT_MINE; +} + +int FLVDecoderCreator::HandlesVideo(int format_type) +{ + if (format_type == FLV::VIDEO_FORMAT_AVC) + { + return CREATEDECODER_SUCCESS; + } + return CREATEDECODER_NOT_MINE; +} + +#define CBCLASS FLVDecoderCreator +START_DISPATCH; +CB(CREATE_VIDEO_DECODER, CreateVideoDecoder) +CB(HANDLES_VIDEO, HandlesVideo) +END_DISPATCH; +#undef CBCLASS + +/* --- */ +uint32_t GetNALUSize(uint64_t nalu_size_bytes, const uint8_t *h264_data, size_t data_len); +uint32_t Read24(const uint8_t *data); + +FLVH264::FLVH264(MFTDecoder *decoder) : decoder(decoder) +{ + sequence_headers_parsed=0; + nalu_size_bytes=0; +} + +FLVH264::~FLVH264() +{ + for (size_t i=0;i<buffered_frames.size();i++) { + nullsoft_h264_frame_data frame_data = buffered_frames[i]; + decoder->FreeFrame((YV12_PLANES *)frame_data.data, frame_data.decoder_data); + } + delete decoder; +} + +int FLVH264::GetOutputFormat(int *x, int *y, int *color_format) +{ + UINT width, height; + bool local_flip=false; + double aspect_ratio; + if (SUCCEEDED(decoder->GetOutputFormat(&width, &height, &local_flip, &aspect_ratio))) { + *x = width; + *y = height; + *color_format = '21VY'; + return FLV_VIDEO_SUCCESS; + } + return FLV_VIDEO_FAILURE; +} + +int FLVH264::DecodeSample(const void *inputBuffer, size_t inputBufferBytes, int32_t timestamp) +{ + const uint8_t *h264_data = (const uint8_t *)inputBuffer; + if (*h264_data == 0 && inputBufferBytes >= 10) // sequence headers + { + h264_data++; // skip packet type + uint32_t timestamp_offset = Read24(h264_data); + h264_data+=3; + inputBufferBytes -=4; + h264_data+=4; // don't care about level & profile + inputBufferBytes -=4; + nalu_size_bytes = (*h264_data++ & 0x3)+1; + inputBufferBytes--; + size_t num_sps = *h264_data++ & 0x1F; + inputBufferBytes--; + for (size_t i=0;i!=num_sps;i++) + { + if (inputBufferBytes > 2) + { + uint16_t sps_size = (h264_data[0] << 8) | h264_data[1]; + h264_data+=2; + inputBufferBytes-=2; + //H264_ProcessSPS(decoder, h264_data+1, sps_size); + if (inputBufferBytes >= sps_size) + { + decoder->Feed(h264_data, sps_size, timestamp+timestamp_offset); + h264_data+=sps_size; + inputBufferBytes-=sps_size; + } + } + } + if (inputBufferBytes) + { + size_t num_pps = *h264_data++; + inputBufferBytes--; + for (size_t i=0;i!=num_pps;i++) + { + if (inputBufferBytes > 2) + { + uint16_t sps_size = (h264_data[0] << 8) | h264_data[1]; + h264_data+=2; + inputBufferBytes-=2; + //H264_ProcessPPS(decoder, h264_data+1, sps_size); + if (inputBufferBytes >= sps_size) + { + decoder->Feed(h264_data, sps_size, timestamp+timestamp_offset); + h264_data+=sps_size; + inputBufferBytes-=sps_size; + } + } + } + } + sequence_headers_parsed=1; + } + else if (*h264_data == 1) // frame + { + h264_data++; + inputBufferBytes--; + if (inputBufferBytes < 3) + return FLV_VIDEO_FAILURE; + uint32_t timestamp_offset = Read24(h264_data); + + h264_data+=3; + inputBufferBytes-=3; + + while (inputBufferBytes) + { + uint32_t this_size =GetNALUSize(nalu_size_bytes, h264_data, inputBufferBytes); + if (this_size == 0) + return FLV_VIDEO_FAILURE; + + inputBufferBytes-=nalu_size_bytes; + h264_data+=nalu_size_bytes; + if (this_size > inputBufferBytes) + return FLV_VIDEO_FAILURE; + for (;;) { + HRESULT hr = decoder->Feed(h264_data, this_size, timestamp+timestamp_offset); + if (hr == MF_E_NOTACCEPTING) { + nullsoft_h264_frame_data frame_data; + if (FAILED(decoder->GetFrame((YV12_PLANES **)&frame_data.data, &frame_data.decoder_data, &frame_data.local_timestamp))) { + continue; + } + buffered_frames.push_back(frame_data); + } else if (FAILED(hr)) { + return FLV_VIDEO_FAILURE; + } else { + break; + } + } + + inputBufferBytes-=this_size; + h264_data+=this_size; + } + } + + return FLV_VIDEO_SUCCESS; +} + +void FLVH264::Flush() +{ + for (size_t i=0;i<buffered_frames.size();i++) { + nullsoft_h264_frame_data frame_data = buffered_frames[i]; + decoder->FreeFrame((YV12_PLANES *)frame_data.data, frame_data.decoder_data); + } + decoder->Flush(); +} + +void FLVH264::Close() +{ + delete this; +} + +int FLVH264::GetPicture(void **data, void **decoder_data, uint64_t *timestamp) +{ + if (!buffered_frames.empty()) { + nullsoft_h264_frame_data frame_data = buffered_frames[0]; + buffered_frames.erase(buffered_frames.begin()); + *data = frame_data.data; + *decoder_data = frame_data.decoder_data; + *timestamp = frame_data.local_timestamp; + return FLV_VIDEO_SUCCESS; + } + + if (SUCCEEDED(decoder->GetFrame((YV12_PLANES **)data, decoder_data, timestamp))) { + return FLV_VIDEO_SUCCESS; + } else { + return FLV_VIDEO_FAILURE; + } +} + +void FLVH264::FreePicture(void *data, void *decoder_data) +{ + decoder->FreeFrame((YV12_PLANES *)data, decoder_data); +} + +int FLVH264::Ready() +{ + return sequence_headers_parsed; +} + +#define CBCLASS FLVH264 +START_DISPATCH; +CB(FLV_VIDEO_GETOUTPUTFORMAT, GetOutputFormat) +CB(FLV_VIDEO_DECODE, DecodeSample) +VCB(FLV_VIDEO_FLUSH, Flush) +VCB(FLV_VIDEO_CLOSE, Close) +CB(FLV_VIDEO_GET_PICTURE, GetPicture) +VCB(FLV_VIDEO_FREE_PICTURE, FreePicture) +CB(FLV_VIDEO_READY, Ready) +END_DISPATCH; +#undef CBCLASS + |