root/src/tests/kits/media/mpeg2_decoder_test/mpeg2_decoder_test.cpp
/*
 * 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 85 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;
        }

        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;
}