1 /* 2 * Copyright 2014 Haiku, Inc. All rights reserved. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Colin Günther, coling@gmx.de 7 */ 8 9 10 /*! Tests video stream decoding functionality of the FFMPEG decoder plugin. 11 12 This test is designed with testing the dvb media-addon video decoding 13 capability in mind. Thus we are restricting this test to MPEG2-Video. 14 15 The test requires a MPEG2 test file at the same directory you start the 16 test from. Normally there is a test file included at the same location 17 this source file is located if not have a look at the git history. 18 19 Successful completion of this test results in a series of PNG image 20 files created at the same location you start the test from. 21 22 The originally included test file results in 85 PNG images, 23 representing a movie sequence with the actress Anne Hathaway. 24 This test file has the following properties: 25 - The first frames cannot be decoded, due to missing I-Frames 26 This is by intention, and helps identifying possible bugs in the 27 FFMPEG decoder plugin regarding decoding of video streams, where 28 the stream may start in the middle of a group of pictures (GoP). 29 - encoded_video.output.first_active = 0 30 - encoded_video.output.last_active = 575 31 - encoded_video.output.orientation = B_VIDEO_TOP_LEFT_RIGHT 32 - encoded_video.output.display.format = B_YUV420 33 - encoded_video.output.display.line_width = 720 34 - encoded_video.output.display.line_count = 576 35 36 In any way, there -MUST- be no need to properly initialize those video 37 properties for this test to succeed. To put it in other terms: The 38 FFMPEG decoder plugin should determine those properties by its own and 39 decode the video accordingly. 40 */ 41 42 43 #include <stdlib.h> 44 #include <string.h> 45 46 #include <AppKit.h> 47 #include <InterfaceKit.h> 48 #include <MediaKit.h> 49 #include <SupportKit.h> 50 #include <TranslationKit.h> 51 52 #include <media/Buffer.h> 53 #include <media/BufferGroup.h> 54 #include <media/MediaDecoder.h> 55 #include <storage/File.h> 56 #include <support/Errors.h> 57 58 59 extern "C" { 60 #include "avcodec.h" 61 62 #ifdef DEBUG 63 // Needed to fix debug build, otherwise the linker complains about 64 // "undefined reference to `ff_log2_tab'" 65 const uint8_t ff_log2_tab[256] = {0}; 66 #endif 67 68 } // extern "C" 69 70 71 const char* kTestVideoFilename = "./AVCodecTestMpeg2VideoStreamRaw"; 72 73 74 class FileDecoder : public BMediaDecoder { 75 private: 76 BFile* sourceFile; 77 78 public: 79 FileDecoder(BFile* file) : BMediaDecoder() { 80 sourceFile = file; 81 } 82 83 protected: 84 virtual status_t GetNextChunk(const void **chunkData, size_t* chunkLen, 85 media_header* mh) { 86 static const uint kReadSizeInBytes = 4096; 87 88 memset(mh, 0, sizeof(media_header)); 89 90 void* fileData = malloc(kReadSizeInBytes); 91 ssize_t readLength = this->sourceFile->Read(fileData, 92 kReadSizeInBytes); 93 if (readLength < 0) 94 return B_ERROR; 95 96 if (readLength == 0) 97 return B_LAST_BUFFER_ERROR; 98 99 *chunkData = fileData; 100 *chunkLen = readLength; 101 102 return B_OK; 103 } 104 }; 105 106 107 media_format* CreateMpeg2MediaFormat(); 108 media_format CreateRawMediaFormat(); 109 void WriteBufferToImageFileAndRecycleIt(BBuffer* buffer); 110 BBitmap* CreateBitmapUsingMediaFormat(media_format mediaFormat); 111 status_t SaveBitmapAtPathUsingFormat(BBitmap* bitmap, BString* filePath, 112 uint32 bitmapStorageFormat); 113 114 115 int 116 main(int argc, char* argv[]) 117 { 118 BApplication app("application/x-vnd.mpeg2-decoder-test"); 119 120 BFile* mpeg2EncodedFile = new BFile(kTestVideoFilename, O_RDONLY); 121 BMediaDecoder* mpeg2Decoder = new FileDecoder(mpeg2EncodedFile); 122 123 media_format* mpeg2MediaFormat = CreateMpeg2MediaFormat(); 124 mpeg2Decoder->SetTo(mpeg2MediaFormat); 125 status_t settingMpeg2DecoderStatus = mpeg2Decoder->InitCheck(); 126 if (settingMpeg2DecoderStatus < B_OK) 127 exit(1); 128 129 media_format rawMediaFormat = CreateRawMediaFormat(); 130 status_t settingMpeg2DecoderOutputStatus 131 = mpeg2Decoder->SetOutputFormat(&rawMediaFormat); 132 if (settingMpeg2DecoderOutputStatus < B_OK) 133 exit(2); 134 135 static const uint32 kVideoBufferRequestTimeout = 20000; 136 uint32 videoBufferSizeInBytesMax = 720 * 576 * 4; 137 BBufferGroup* rawVideoFramesGroup 138 = new BBufferGroup(videoBufferSizeInBytesMax, 4); 139 BBuffer* rawVideoFrame = NULL; 140 141 int64 rawVideoFrameCount = 0; 142 media_header mh; 143 while (true) { 144 rawVideoFrame = rawVideoFramesGroup->RequestBuffer( 145 videoBufferSizeInBytesMax, kVideoBufferRequestTimeout); 146 status_t decodingVideoFrameStatus = mpeg2Decoder->Decode( 147 rawVideoFrame->Data(), &rawVideoFrameCount, &mh, NULL); 148 if (decodingVideoFrameStatus < B_OK) { 149 rawVideoFrame->Recycle(); 150 break; 151 } 152 153 WriteBufferToImageFileAndRecycleIt(rawVideoFrame); 154 } 155 156 // Cleaning up 157 rawVideoFramesGroup->ReclaimAllBuffers(); 158 } 159 160 161 /*! The caller takes ownership of the returned media_format value. 162 Thus the caller needs to free the returned value. 163 The returned value may be NULL, when there was an error. 164 */ 165 media_format* 166 CreateMpeg2MediaFormat() 167 { 168 // The following code is mainly copy 'n' paste from src/add-ons/media/ 169 // media-add-ons/dvb/MediaFormat.cpp:GetHeaderFormatMpegVideo() 170 171 status_t status; 172 media_format_description desc; 173 desc.family = B_MISC_FORMAT_FAMILY; 174 desc.u.misc.file_format = 'ffmp'; 175 desc.u.misc.codec = CODEC_ID_MPEG2VIDEO; 176 static media_format* kFailedToCreateMpeg2MediaFormat = NULL; 177 178 BMediaFormats formats; 179 status = formats.InitCheck(); 180 if (status < B_OK) { 181 printf("formats.InitCheck failed, error %lu\n", status); 182 return kFailedToCreateMpeg2MediaFormat; 183 } 184 185 media_format* mpeg2MediaFormat 186 = static_cast<media_format*>(malloc(sizeof(media_format))); 187 memset(mpeg2MediaFormat, 0, sizeof(media_format)); 188 status = formats.GetFormatFor(desc, mpeg2MediaFormat); 189 if (status) { 190 printf("formats.GetFormatFor failed, error %lu\n", status); 191 free(mpeg2MediaFormat); 192 return kFailedToCreateMpeg2MediaFormat; 193 } 194 195 return mpeg2MediaFormat; 196 } 197 198 199 media_format 200 CreateRawMediaFormat() 201 { 202 media_format rawMediaFormat; 203 memset(&rawMediaFormat, 0, sizeof(media_format)); 204 205 rawMediaFormat.type = B_MEDIA_RAW_VIDEO; 206 rawMediaFormat.u.raw_video.display.format = B_RGB32; 207 rawMediaFormat.u.raw_video.display.line_width = 720; 208 rawMediaFormat.u.raw_video.display.line_count = 576; 209 rawMediaFormat.u.raw_video.last_active 210 = rawMediaFormat.u.raw_video.display.line_count - 1; 211 rawMediaFormat.u.raw_video.display.bytes_per_row 212 = rawMediaFormat.u.raw_video.display.line_width * 4; 213 rawMediaFormat.u.raw_video.field_rate = 0; // wildcard 214 rawMediaFormat.u.raw_video.interlace = 1; 215 rawMediaFormat.u.raw_video.first_active = 0; 216 rawMediaFormat.u.raw_video.orientation = B_VIDEO_TOP_LEFT_RIGHT; 217 rawMediaFormat.u.raw_video.pixel_width_aspect = 1; 218 rawMediaFormat.u.raw_video.pixel_height_aspect = 1; 219 rawMediaFormat.u.raw_video.display.pixel_offset = 0; 220 rawMediaFormat.u.raw_video.display.line_offset = 0; 221 rawMediaFormat.u.raw_video.display.flags = 0; 222 223 return rawMediaFormat; 224 } 225 226 227 void 228 WriteBufferToImageFileAndRecycleIt(BBuffer* buffer) 229 { 230 static BBitmap* image = NULL; 231 232 if (image == NULL) { 233 // Lazy initialization 234 image = CreateBitmapUsingMediaFormat(CreateRawMediaFormat()); 235 } 236 237 if (image == NULL) { 238 // Failed to create the image, needed for converting the buffer 239 buffer->Recycle(); 240 return; 241 } 242 243 memcpy(image->Bits(), buffer->Data(), image->BitsLength()); 244 buffer->Recycle(); 245 246 static int32 imageSavedCounter = 0; 247 static const char* kImageFileNameTemplate = "./mpeg2TestImage%d.png"; 248 BString imageFileName; 249 imageFileName.SetToFormat(kImageFileNameTemplate, imageSavedCounter); 250 251 status_t savingBitmapStatus = SaveBitmapAtPathUsingFormat(image, 252 &imageFileName, B_PNG_FORMAT); 253 if (savingBitmapStatus >= B_OK) 254 imageSavedCounter++; 255 } 256 257 258 BBitmap* 259 CreateBitmapUsingMediaFormat(media_format mediaFormat) 260 { 261 const uint32 kNoFlags = 0; 262 BBitmap* creatingBitmapFailed = NULL; 263 264 float imageWidth = mediaFormat.u.raw_video.display.line_width; 265 float imageHeight = mediaFormat.u.raw_video.display.line_count; 266 BRect imageFrame(0, 0, imageWidth - 1, imageHeight - 1); 267 color_space imageColorSpace = mediaFormat.u.raw_video.display.format; 268 269 BBitmap* bitmap = NULL; 270 271 bitmap = new BBitmap(imageFrame, kNoFlags, imageColorSpace); 272 if (bitmap == NULL) 273 return bitmap; 274 275 if (bitmap->InitCheck() < B_OK) { 276 delete bitmap; 277 return creatingBitmapFailed; 278 } 279 280 if (bitmap->IsValid() == false) { 281 delete bitmap; 282 return creatingBitmapFailed; 283 } 284 285 return bitmap; 286 } 287 288 289 status_t 290 SaveBitmapAtPathUsingFormat(BBitmap* bitmap, BString* filePath, 291 uint32 bitmapStorageFormat) 292 { 293 BFile file(*filePath, B_CREATE_FILE | B_ERASE_FILE | B_WRITE_ONLY); 294 status_t creatingFileStatus = file.InitCheck(); 295 if (creatingFileStatus < B_OK) 296 return creatingFileStatus; 297 298 BBitmapStream bitmapStream(bitmap); 299 static BTranslatorRoster* translatorRoster = NULL; 300 if (translatorRoster == NULL) 301 translatorRoster = BTranslatorRoster::Default(); 302 303 status_t writingBitmapToFileStatus = translatorRoster->Translate( 304 &bitmapStream, NULL, NULL, &file, bitmapStorageFormat, 305 B_TRANSLATOR_BITMAP); 306 bitmapStream.DetachBitmap(&bitmap); 307 308 return writingBitmapToFileStatus; 309 } 310