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:
FileDecoder(BFile * file)79 FileDecoder(BFile* file) : BMediaDecoder() {
80 sourceFile = file;
81 }
82
83 protected:
GetNextChunk(const void ** chunkData,size_t * chunkLen,media_header * mh)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
main(int argc,char * argv[])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*
CreateMpeg2MediaFormat()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
CreateRawMediaFormat()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
WriteBufferToImageFileAndRecycleIt(BBuffer * buffer)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*
CreateBitmapUsingMediaFormat(media_format mediaFormat)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
SaveBitmapAtPathUsingFormat(BBitmap * bitmap,BString * filePath,uint32 bitmapStorageFormat)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