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
59extern "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
71const char* kTestVideoFilename = "./AVCodecTestMpeg2VideoStreamRaw";
72
73
74class FileDecoder : public BMediaDecoder {
75private:
76	BFile* sourceFile;
77
78public:
79	FileDecoder(BFile* file) : BMediaDecoder() {
80		sourceFile = file;
81	}
82
83protected:
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
107media_format* CreateMpeg2MediaFormat();
108media_format CreateRawMediaFormat();
109void WriteBufferToImageFileAndRecycleIt(BBuffer* buffer);
110BBitmap* CreateBitmapUsingMediaFormat(media_format mediaFormat);
111status_t SaveBitmapAtPathUsingFormat(BBitmap* bitmap, BString* filePath,
112	uint32 bitmapStorageFormat);
113
114
115int
116main(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*/
165media_format*
166CreateMpeg2MediaFormat()
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
199media_format
200CreateRawMediaFormat()
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
227void
228WriteBufferToImageFileAndRecycleIt(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
258BBitmap*
259CreateBitmapUsingMediaFormat(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
289status_t
290SaveBitmapAtPathUsingFormat(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