xref: /haiku/src/tests/kits/media/mpeg2_decoder_test/mpeg2_decoder_test.cpp (revision 96dbc7d53352b7360ca11ddf939ceaf7f54a61e9)
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