Media Kit: Add MPEG2 video stream decoder test.
- This test currently fails and thus showing that there is something wrong in the Media Kit. - This test was first developed and tested on Haiku R1 Alpha 4.1 where every thing was hacked together to make this test succeed, thus showing that the current Media Kit state of affair has some issues and not this test :) - The test comes with documentation (@see mpeg2_decoder.test.cpp). Signed-off-by: Colin Günther <[email protected]>
This commit is contained in:
@@ -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 ;
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.1 MiB |
@@ -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 ] ;
|
||||
@@ -0,0 +1,319 @@
|
||||
/*
|
||||
* Copyright 2014 Haiku, Inc. All rights reserved.
|
||||
* Distributed under the terms of the MIT License.
|
||||
*
|
||||
* Authors:
|
||||
* Colin Günther, [email protected]
|
||||
*/
|
||||
|
||||
|
||||
/*! 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 <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <AppKit.h>
|
||||
#include <InterfaceKit.h>
|
||||
#include <MediaKit.h>
|
||||
#include <SupportKit.h>
|
||||
#include <TranslationKit.h>
|
||||
|
||||
#include <media/Buffer.h>
|
||||
#include <media/BufferGroup.h>
|
||||
#include <media/MediaDecoder.h>
|
||||
#include <storage/File.h>
|
||||
#include <support/Errors.h>
|
||||
|
||||
|
||||
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<media_format*>(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;
|
||||
}
|
||||
Reference in New Issue
Block a user