diff --git a/src/tests/kits/media/Jamfile b/src/tests/kits/media/Jamfile index 3fe7c21801..72923007d5 100644 --- a/src/tests/kits/media/Jamfile +++ b/src/tests/kits/media/Jamfile @@ -18,6 +18,7 @@ SimpleTest mediaDescriptions : : media ; SubInclude HAIKU_TOP src tests kits media media_decoder ; +SubInclude HAIKU_TOP src tests kits media mpeg2_decoder_test ; SubInclude HAIKU_TOP src tests kits media notificationtest ; SubInclude HAIKU_TOP src tests kits media nodetest ; SubInclude HAIKU_TOP src tests kits media playwav ; diff --git a/src/tests/kits/media/mpeg2_decoder_test/AVCodecTestMpeg2VideoStreamRaw b/src/tests/kits/media/mpeg2_decoder_test/AVCodecTestMpeg2VideoStreamRaw new file mode 100644 index 0000000000..feef047a84 Binary files /dev/null and b/src/tests/kits/media/mpeg2_decoder_test/AVCodecTestMpeg2VideoStreamRaw differ diff --git a/src/tests/kits/media/mpeg2_decoder_test/Jamfile b/src/tests/kits/media/mpeg2_decoder_test/Jamfile new file mode 100644 index 0000000000..57b47bebf0 --- /dev/null +++ b/src/tests/kits/media/mpeg2_decoder_test/Jamfile @@ -0,0 +1,17 @@ +SubDir HAIKU_TOP src tests kits media mpeg2_decoder_test ; + +local ffmpegHeaders = [ BuildFeatureAttribute ffmpeg : headers : path ] ; +UseHeaders $(ffmpegHeaders) ; +UseHeaders [ FDirName $(ffmpegHeaders) libavcodec ] ; +UseHeaders [ FDirName $(ffmpegHeaders) libavformat ] ; +UseHeaders [ FDirName $(ffmpegHeaders) libavutil ] ; + +SubDirC++Flags -D__STDC_CONSTANT_MACROS -Wno-deprecated-declarations ; + +SimpleTest mpeg2_decoder_test : + mpeg2_decoder_test.cpp + : be media translation $(TARGET_LIBSUPC++) +; + +Includes [ FGristFiles mpeg2_decoder_test.cpp ] + : [ BuildFeatureAttribute ffmpeg : headers ] ; diff --git a/src/tests/kits/media/mpeg2_decoder_test/mpeg2_decoder_test.cpp b/src/tests/kits/media/mpeg2_decoder_test/mpeg2_decoder_test.cpp new file mode 100644 index 0000000000..c943abb8b8 --- /dev/null +++ b/src/tests/kits/media/mpeg2_decoder_test/mpeg2_decoder_test.cpp @@ -0,0 +1,319 @@ +/* + * Copyright 2014 Haiku, Inc. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Colin Günther, coling@gmx.de + */ + + +/*! Tests video stream decoding functionality of the FFMPEG decoder plugin. + + This test is designed with testing the dvb media-addon video decoding + capability in mind. Thus we are restricting this test to MPEG2-Video. + + The test requires a MPEG2 test file at the same directory you start the + test from. Normally there is a test file included at the same location + this source file is located if not have a look at the git history. + + Successful completion of this test results in a series of PNG image + files created at the same location you start the test from. + + The originally included test file results in 84 PNG images, + representing a movie sequence with the actress Anne Hathaway. + This test file has the following properties: + - The first frames cannot be decoded, due to missing I-Frames + This is by intention, and helps identifying possible bugs in the + FFMPEG decoder plugin regarding decoding of video streams, where + the stream may start in the middle of a group of pictures (GoP). + - encoded_video.output.first_active = 0 + - encoded_video.output.last_active = 575 + - encoded_video.output.orientation = B_VIDEO_TOP_LEFT_RIGHT + - encoded_video.output.display.format = B_YUV420 + - encoded_video.output.display.line_width = 720 + - encoded_video.output.display.line_count = 576 + + In any way, there -MUST- be no need to properly initialize those video + properties for this test to succeed. To put it in other terms: The + FFMPEG decoder plugin should determine those properties by its own and + decode the video accordingly. +*/ + + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + + +extern "C" { + #include "avcodec.h" + +#ifdef DEBUG + // Needed to fix debug build, otherwise the linker complains about + // "undefined reference to `ff_log2_tab'" + const uint8_t ff_log2_tab[256] = {0}; +#endif + +} // extern "C" + + +const char* kTestVideoFilename = "./AVCodecTestMpeg2VideoStreamRaw"; + + +class FileDecoder : public BMediaDecoder { +private: + BFile* sourceFile; + +public: + FileDecoder(BFile* file) : BMediaDecoder() { + sourceFile = file; + } + +protected: + virtual status_t GetNextChunk(const void **chunkData, size_t* chunkLen, + media_header* mh) { + static const uint kReadSizeInBytes = 4096; + + memset(mh, 0, sizeof(media_header)); + + void* fileData = malloc(kReadSizeInBytes); + ssize_t readLength = this->sourceFile->Read(fileData, + kReadSizeInBytes); + if (readLength < 0) + return B_ERROR; + + if (readLength == 0) + return B_LAST_BUFFER_ERROR; + + *chunkData = fileData; + *chunkLen = readLength; + + return B_OK; + } +}; + + +media_format* CreateMpeg2MediaFormat(); +media_format CreateRawMediaFormat(); +void WriteBufferToImageFileAndRecycleIt(BBuffer* buffer); +BBitmap* CreateBitmapUsingMediaFormat(media_format mediaFormat); +status_t SaveBitmapAtPathUsingFormat(BBitmap* bitmap, BString* filePath, + uint32 bitmapStorageFormat); + + +int +main(int argc, char* argv[]) +{ + BApplication app("application/x-vnd.mpeg2-decoder-test"); + + BFile* mpeg2EncodedFile = new BFile(kTestVideoFilename, O_RDONLY); + BMediaDecoder* mpeg2Decoder = new FileDecoder(mpeg2EncodedFile); + + media_format* mpeg2MediaFormat = CreateMpeg2MediaFormat(); + mpeg2Decoder->SetTo(mpeg2MediaFormat); + status_t settingMpeg2DecoderStatus = mpeg2Decoder->InitCheck(); + if (settingMpeg2DecoderStatus < B_OK) + exit(1); + + media_format rawMediaFormat = CreateRawMediaFormat(); + status_t settingMpeg2DecoderOutputStatus + = mpeg2Decoder->SetOutputFormat(&rawMediaFormat); + if (settingMpeg2DecoderOutputStatus < B_OK) + exit(2); + + static const uint32 kVideoBufferRequestTimeout = 20000; + uint32 videoBufferSizeInBytesMax = 720 * 576 * 4; + BBufferGroup* rawVideoFramesGroup + = new BBufferGroup(videoBufferSizeInBytesMax, 4); + BBuffer* rawVideoFrame = NULL; + + int64 rawVideoFrameCount = 0; + media_header mh; + while (true) { + rawVideoFrame = rawVideoFramesGroup->RequestBuffer( + videoBufferSizeInBytesMax, kVideoBufferRequestTimeout); + status_t decodingVideoFrameStatus = mpeg2Decoder->Decode( + rawVideoFrame->Data(), &rawVideoFrameCount, &mh, NULL); + if (decodingVideoFrameStatus < B_OK) { + rawVideoFrame->Recycle(); + break; + } + + WriteBufferToImageFileAndRecycleIt(rawVideoFrame); + } + + // Cleaning up + rawVideoFramesGroup->ReclaimAllBuffers(); +} + + +/*! The caller takes ownership of the returned media_format value. + Thus the caller needs to free the returned value. + The returned value may be NULL, when there was an error. +*/ +media_format* +CreateMpeg2MediaFormat() +{ + // The following code is mainly copy 'n' paste from src/add-ons/media/ + // media-add-ons/dvb/MediaFormat.cpp:GetHeaderFormatMpegVideo() + + status_t status; + media_format_description desc; + desc.family = B_MISC_FORMAT_FAMILY; + desc.u.misc.file_format = 'ffmp'; + desc.u.misc.codec = CODEC_ID_MPEG2VIDEO; + static media_format* kFailedToCreateMpeg2MediaFormat = NULL; + + BMediaFormats formats; + status = formats.InitCheck(); + if (status < B_OK) { + printf("formats.InitCheck failed, error %lu\n", status); + return kFailedToCreateMpeg2MediaFormat; + } + + media_format* mpeg2MediaFormat + = static_cast(malloc(sizeof(media_format))); + memset(mpeg2MediaFormat, 0, sizeof(media_format)); + status = formats.GetFormatFor(desc, mpeg2MediaFormat); + if (status) { + printf("formats.GetFormatFor failed, error %lu\n", status); + free(mpeg2MediaFormat); + return kFailedToCreateMpeg2MediaFormat; + } + + // The following code block can be removed, once the ffmpeg addon can + // determine the codec output parameters from the encoded data. + mpeg2MediaFormat->u.encoded_video.output.first_active = 0; + mpeg2MediaFormat->u.encoded_video.output.last_active = 575; + mpeg2MediaFormat->u.encoded_video.output.orientation + = B_VIDEO_TOP_LEFT_RIGHT; + mpeg2MediaFormat->u.encoded_video.output.display.format = B_YUV420; + mpeg2MediaFormat->u.encoded_video.output.display.line_width = 720; + mpeg2MediaFormat->u.encoded_video.output.display.line_count = 576; + + return mpeg2MediaFormat; +} + + +media_format +CreateRawMediaFormat() +{ + media_format rawMediaFormat; + memset(&rawMediaFormat, 0, sizeof(media_format)); + + rawMediaFormat.type = B_MEDIA_RAW_VIDEO; + rawMediaFormat.u.raw_video.display.format = B_RGB32; + rawMediaFormat.u.raw_video.display.line_width = 720; + rawMediaFormat.u.raw_video.display.line_count = 576; + rawMediaFormat.u.raw_video.last_active + = rawMediaFormat.u.raw_video.display.line_count - 1; + rawMediaFormat.u.raw_video.display.bytes_per_row + = rawMediaFormat.u.raw_video.display.line_width * 4; + rawMediaFormat.u.raw_video.field_rate = 0; // wildcard + rawMediaFormat.u.raw_video.interlace = 1; + rawMediaFormat.u.raw_video.first_active = 0; + rawMediaFormat.u.raw_video.orientation = B_VIDEO_TOP_LEFT_RIGHT; + rawMediaFormat.u.raw_video.pixel_width_aspect = 1; + rawMediaFormat.u.raw_video.pixel_height_aspect = 1; + rawMediaFormat.u.raw_video.display.pixel_offset = 0; + rawMediaFormat.u.raw_video.display.line_offset = 0; + rawMediaFormat.u.raw_video.display.flags = 0; + + return rawMediaFormat; +} + + +void +WriteBufferToImageFileAndRecycleIt(BBuffer* buffer) +{ + static BBitmap* image = NULL; + + if (image == NULL) { + // Lazy initialization + image = CreateBitmapUsingMediaFormat(CreateRawMediaFormat()); + } + + if (image == NULL) { + // Failed to create the image, needed for converting the buffer + buffer->Recycle(); + return; + } + + memcpy(image->Bits(), buffer->Data(), image->BitsLength()); + buffer->Recycle(); + + static int32 imageSavedCounter = 0; + static const char* kImageFileNameTemplate = "./mpeg2TestImage%d.png"; + BString imageFileName; + imageFileName.SetToFormat(kImageFileNameTemplate, imageSavedCounter); + + status_t savingBitmapStatus = SaveBitmapAtPathUsingFormat(image, + &imageFileName, B_PNG_FORMAT); + if (savingBitmapStatus >= B_OK) + imageSavedCounter++; +} + + +BBitmap* +CreateBitmapUsingMediaFormat(media_format mediaFormat) +{ + const uint32 kNoFlags = 0; + BBitmap* creatingBitmapFailed = NULL; + + float imageWidth = mediaFormat.u.raw_video.display.line_width; + float imageHeight = mediaFormat.u.raw_video.display.line_count; + BRect imageFrame(0, 0, imageWidth - 1, imageHeight - 1); + color_space imageColorSpace = mediaFormat.u.raw_video.display.format; + + BBitmap* bitmap = NULL; + + bitmap = new BBitmap(imageFrame, kNoFlags, imageColorSpace); + if (bitmap == NULL) + return bitmap; + + if (bitmap->InitCheck() < B_OK) { + delete bitmap; + return creatingBitmapFailed; + } + + if (bitmap->IsValid() == false) { + delete bitmap; + return creatingBitmapFailed; + } + + return bitmap; +} + + +status_t +SaveBitmapAtPathUsingFormat(BBitmap* bitmap, BString* filePath, + uint32 bitmapStorageFormat) +{ + BFile file(*filePath, B_CREATE_FILE | B_ERASE_FILE | B_WRITE_ONLY); + status_t creatingFileStatus = file.InitCheck(); + if (creatingFileStatus < B_OK) + return creatingFileStatus; + + BBitmapStream bitmapStream(bitmap); + static BTranslatorRoster* translatorRoster = NULL; + if (translatorRoster == NULL) + translatorRoster = BTranslatorRoster::Default(); + + status_t writingBitmapToFileStatus = translatorRoster->Translate( + &bitmapStream, NULL, NULL, &file, bitmapStorageFormat, + B_TRANSLATOR_BITMAP); + bitmapStream.DetachBitmap(&bitmap); + + return writingBitmapToFileStatus; +}