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 audio stream decoding functionality of the FFMPEG decoder plugin.
11
12	This test is designed with testing the dvb media-addon audio decoding
13	capability in mind. Thus we are restricting this test to MP3-Audio.
14
15	The test requires a MP3 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 audio being played on the
20	standard system audio output. So turn on your speakers or put on your head
21	phones.
22
23	The originally included test file results in an audio signal containing
24	jingles.
25	This test file has the following properties:
26		- encoded_audio.output.frame_rate = 48000
27		- encoded_audio.output.channel_count = 2
28		- encoded_audio.output.buffer_size = 1024
29		- encoded_audio.output.format = media_raw_audio_format::B_AUDIO_SHORT
30
31	In any way, there -MUST- be no need to properly initialize those audio
32	properties for this test to succeed. To put it in other terms: The
33	FFMPEG decoder plugin should determine those properties by its own and
34	decode the audio accordingly.
35
36*/
37
38
39#include <pthread.h>
40#include <stdlib.h>
41#include <string.h>
42
43#include <AppKit.h>
44#include <InterfaceKit.h>
45#include <MediaKit.h>
46#include <SupportKit.h>
47
48#include <media/Buffer.h>
49#include <media/BufferGroup.h>
50#include <media/MediaDecoder.h>
51#include <media/Sound.h>
52#include <media/SoundPlayer.h>
53#include <storage/File.h>
54#include <support/Errors.h>
55
56
57extern "C" {
58	#include "avcodec.h"
59
60#ifdef DEBUG
61  // Needed to fix debug build, otherwise the linker complains about
62  // "undefined reference to `ff_log2_tab'"
63  const uint8_t ff_log2_tab[256] = {0};
64#endif
65
66}  // extern "C"
67
68
69const char*	kTestAudioFilename = "./AVCodecTestMp3AudioStreamRaw";
70
71
72class FileDecoder : public BMediaDecoder {
73private:
74	BFile* sourceFile;
75
76public:
77	FileDecoder(BFile* file) : BMediaDecoder() {
78		sourceFile = file;
79	}
80
81protected:
82	virtual status_t	GetNextChunk(const void** chunkData, size_t* chunkLen,
83							media_header* mh) {
84		static const uint kReadSizeInBytes = 4096;
85
86		memset(mh, 0, sizeof(media_header));
87
88		void* fileData = malloc(kReadSizeInBytes);
89		ssize_t readLength = this->sourceFile->Read(fileData,
90			kReadSizeInBytes);
91		if (readLength < 0)
92			return B_ERROR;
93
94		if (readLength == 0)
95			return B_LAST_BUFFER_ERROR;
96
97		*chunkData = fileData;
98		*chunkLen = readLength;
99
100		return B_OK;
101	}
102};
103
104
105typedef struct cookie_decode {
106	BFile* inputFile;
107	BMediaDecoder* decoder;
108	BBufferGroup* decodedDataGroup;
109	uint32 decodedDataBufferSizeMax;
110	pthread_cond_t playingFinishedCondition;
111} cookie_decode;
112
113
114status_t InitializeMp3DecodingCookie(cookie_decode* cookie);
115void FreeMp3DecodingCookie(cookie_decode* cookie);
116media_format* CreateMp3MediaFormat();
117media_format CreateRawMediaFormat();
118void Mp3Decoding(void* cookie, void* buffer, size_t bufferSize,
119	const media_raw_audio_format& format);
120
121
122int
123main(int argc, char* argv[])
124{
125	BApplication app("application/x-vnd.mp3-decoder-test");
126
127	cookie_decode decodingCookie;
128	if (InitializeMp3DecodingCookie(&decodingCookie) != B_OK)
129		exit(1);
130
131	media_format rawAudioFormat = CreateRawMediaFormat();
132
133	media_raw_audio_format* audioOutputFormat
134		= &rawAudioFormat.u.raw_audio;
135
136	BSoundPlayer player(audioOutputFormat, "wave_player", Mp3Decoding,
137		NULL, &decodingCookie);
138	player.Start();
139	player.SetHasData(true);
140	player.SetVolume(0.5);
141
142	// Wait as long as we are playing sound
143	pthread_mutex_t playingFinishedMutex;
144	pthread_mutex_init(&playingFinishedMutex, NULL);
145	pthread_mutex_lock(&playingFinishedMutex);
146	pthread_cond_wait(&decodingCookie.playingFinishedCondition,
147		&playingFinishedMutex);
148
149	player.SetHasData(false);
150	player.Stop();
151
152	// Cleaning up
153	FreeMp3DecodingCookie(&decodingCookie);
154	pthread_mutex_destroy(&playingFinishedMutex);
155}
156
157
158status_t
159InitializeMp3DecodingCookie(cookie_decode* cookie)
160{
161	cookie->inputFile = new BFile(kTestAudioFilename, O_RDONLY);
162	cookie->decoder = new FileDecoder(cookie->inputFile);
163
164	media_format* mp3MediaFormat = CreateMp3MediaFormat();
165	cookie->decoder->SetTo(mp3MediaFormat);
166	status_t settingDecoderStatus = cookie->decoder->InitCheck();
167	if (settingDecoderStatus < B_OK)
168		return B_ERROR;
169
170	media_format rawMediaFormat = CreateRawMediaFormat();
171	status_t settingDecoderOutputStatus
172		= cookie->decoder->SetOutputFormat(&rawMediaFormat);
173	if (settingDecoderOutputStatus < B_OK)
174		return B_ERROR;
175
176	cookie->decodedDataBufferSizeMax
177		= rawMediaFormat.u.raw_audio.buffer_size * 3;
178	cookie->decodedDataGroup
179		= new BBufferGroup(cookie->decodedDataBufferSizeMax, 25);
180
181	if (pthread_cond_init(&cookie->playingFinishedCondition, NULL) < 0)
182		return B_ERROR;
183
184	return B_OK;
185}
186
187
188void
189FreeMp3DecodingCookie(cookie_decode* cookie)
190{
191	pthread_cond_destroy(&cookie->playingFinishedCondition);
192	cookie->decodedDataGroup->ReclaimAllBuffers();
193	free(cookie->decodedDataGroup);
194	free(cookie->decoder);
195	free(cookie->inputFile);
196}
197
198
199/*!	The caller takes ownership of the returned media_format value.
200	Thus the caller needs to free the returned value.
201	The returned value may be NULL, when there was an error.
202*/
203media_format*
204CreateMp3MediaFormat()
205{
206	// copy 'n' paste from src/add-ons/media/media-add-ons/dvb/MediaFormat.cpp:
207	// GetHeaderFormatMpegAudio()
208	status_t status;
209	media_format_description desc;
210	desc.family = B_MISC_FORMAT_FAMILY;
211	desc.u.misc.file_format = 'ffmp';
212	desc.u.misc.codec = CODEC_ID_MP3;
213	static media_format* sNoMp3MediaFormat = NULL;
214
215	BMediaFormats formats;
216	status = formats.InitCheck();
217	if (status < B_OK) {
218		printf("formats.InitCheck failed, error %lu\n", status);
219		return sNoMp3MediaFormat;
220	}
221
222	media_format* mp3MediaFormat
223		= static_cast<media_format*>(malloc(sizeof(media_format)));
224	memset(mp3MediaFormat, 0, sizeof(media_format));
225	status = formats.GetFormatFor(desc, mp3MediaFormat);
226	if (status < B_OK) {
227		printf("formats.GetFormatFor failed, error %lu\n", status);
228		return sNoMp3MediaFormat;
229	}
230
231	return mp3MediaFormat;
232}
233
234
235media_format
236CreateRawMediaFormat()
237{
238	media_format rawMediaFormat;
239	memset(&rawMediaFormat, 0, sizeof(media_format));
240
241	rawMediaFormat.type = B_MEDIA_RAW_AUDIO;
242	rawMediaFormat.u.raw_audio.frame_rate = 48000;
243	rawMediaFormat.u.raw_audio.channel_count = 2;
244	rawMediaFormat.u.raw_audio.format = media_raw_audio_format::B_AUDIO_SHORT;
245	rawMediaFormat.u.raw_audio.byte_order = B_MEDIA_HOST_ENDIAN;
246	rawMediaFormat.u.raw_audio.buffer_size = 32768;
247		// comment from src/add-ons/media/media-add-ons/dvb/
248		// DVBMediaNode.cpp::InitDefaultFormats(): when set to anything
249		// different from 32768 haiku mixer has problems
250
251	return rawMediaFormat;
252}
253
254
255void
256Mp3Decoding(void* cookie, void* buffer, size_t bufferSize,
257	const media_raw_audio_format& format)
258{
259	cookie_decode* decodingCookie = static_cast<cookie_decode*>(cookie);
260	int64 rawAudioFrameCount = 0;
261	media_header mh;
262	status_t decodingAudioFramesStatus
263		= decodingCookie->decoder->Decode(buffer, &rawAudioFrameCount, &mh,
264			NULL);
265	if (decodingAudioFramesStatus < B_OK) {
266		sleep(2);
267			// Give the player some time to catch up playing all decoded data.
268			// The player may still have some data to play, even though we run
269			// out of new data, so don't just tell that we finished playing
270			// quiet yet.
271		pthread_cond_signal(&decodingCookie->playingFinishedCondition);
272	}
273}
274