changeset 3:e6cf9f26d0e7

Added a separate "transcode" app for testing transcoding
author fraserofthenight
date Wed, 08 Jul 2009 07:29:48 -0700
parents 52278c5dc19c
children a1202aac1124
files .classpath .hgignore .settings/Hoofbaby - Transcode.launch .settings/descent.ui.prefs ref/codec_adhoc.d src/build/frankenbuild.d src/impl/hoofbaby/app/codec_adhoc.d src/impl/hoofbaby/app/main.d src/test/hoofbaby/test/adhoc/transcode.d
diffstat 9 files changed, 634 insertions(+), 315 deletions(-) [+]
line wrap: on
line diff
--- a/.classpath	Tue Jul 07 20:23:49 2009 -0700
+++ b/.classpath	Wed Jul 08 07:29:48 2009 -0700
@@ -2,6 +2,8 @@
 <classpath>
 	<classpathentry kind="src" path="src/impl"/>
 	<classpathentry kind="src" path="src/build"/>
+	<classpathentry kind="src" path="src/test"/>
+	<classpathentry kind="src" path="src/tools"/>
 	<classpathentry kind="con" path="descent.USER_LIBRARY/Tango"/>
 	<classpathentry kind="output" path="bin"/>
 </classpath>
--- a/.hgignore	Tue Jul 07 20:23:49 2009 -0700
+++ b/.hgignore	Wed Jul 08 07:29:48 2009 -0700
@@ -6,6 +6,7 @@
 *.ncb
 *.suo
 *.user
+*.wmv
 deps/Platinum/Build/Targets/x86-microsoft-win32-vs2008/Platinum/Debug/*
 deps/Platinum/Build/Targets/x86-microsoft-win32-vs2008/Platinum/Release/*
 deps/Platinum/ThirdParty/Neptune/Build\Targets/x86-microsoft-win32-vs2008/Neptune/Debug/*
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.settings/Hoofbaby - Transcode.launch	Wed Jul 08 07:29:48 2009 -0700
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<launchConfiguration type="org.eclipse.ui.externaltools.ProgramLaunchConfigurationType">
+<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${workspace_loc:/hoofbaby/bin/build.bat}"/>
+<stringAttribute key="org.eclipse.ui.externaltools.ATTR_TOOL_ARGUMENTS" value="transcode"/>
+<stringAttribute key="org.eclipse.ui.externaltools.ATTR_WORKING_DIRECTORY" value="${workspace_loc:/hoofbaby/bin}"/>
+</launchConfiguration>
--- a/.settings/descent.ui.prefs	Tue Jul 07 20:23:49 2009 -0700
+++ b/.settings/descent.ui.prefs	Wed Jul 08 07:29:48 2009 -0700
@@ -1,5 +1,5 @@
-#Sun Jun 21 09:02:00 PDT 2009
-descent.ui.text.custom_code_templates=<?xml version\="1.0" encoding\="UTF-8" standalone\="no"?><templates/>
+#Wed Jul 08 07:11:25 PDT 2009
+descent.ui.text.custom_code_templates=<?xml version\="1.0" encoding\="UTF-8" standalone\="no"?><templates><template autoinsert\="true" context\="gettercomment_context" deleted\="false" description\="Comment for getter method" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.gettercomment" name\="gettercomment">/**\r\n * @return the ${bare_field_name}\r\n */</template><template autoinsert\="true" context\="settercomment_context" deleted\="false" description\="Comment for setter method" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.settercomment" name\="settercomment">/**\r\n * @param ${param} the ${bare_field_name} to set\r\n */</template><template autoinsert\="true" context\="constructorcomment_context" deleted\="false" description\="Comment for created constructors" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.constructorcomment" name\="constructorcomment">/**\r\n * ${tags}\r\n */</template><template autoinsert\="true" context\="filecomment_context" deleted\="false" description\="Comment for created Java files" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.filecomment" name\="filecomment">/**\r\n * \r\n */</template><template autoinsert\="true" context\="typecomment_context" deleted\="false" description\="Comment for created types" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.typecomment" name\="typecomment">/**\r\n * @author ${user}\r\n *\r\n * ${tags}\r\n */</template><template autoinsert\="true" context\="fieldcomment_context" deleted\="false" description\="Comment for fields" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.fieldcomment" name\="fieldcomment">/**\r\n * \r\n */</template><template autoinsert\="true" context\="methodcomment_context" deleted\="false" description\="Comment for non-overriding methods" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.methodcomment" name\="methodcomment">/**\r\n * ${tags}\r\n */</template><template autoinsert\="true" context\="overridecomment_context" deleted\="false" description\="Comment for overriding methods" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.overridecomment" name\="overridecomment">/* (non-Javadoc)\r\n * ${see_to_overridden}\r\n */</template><template autoinsert\="true" context\="delegatecomment_context" deleted\="false" description\="Comment for delegate methods" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.delegatecomment" name\="delegatecomment">/**\r\n * ${tags}\r\n * ${see_to_target}\r\n */</template><template autoinsert\="false" context\="newtype_context" deleted\="false" description\="Newly created files" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.newtype" name\="newtype">/**\r\n * Hoofbaby -- http\://www.dsource.org/projects/hoofbaby\r\n * Copyright (C) 2009 Robert Fraser\r\n * \r\n * This program is free software; you can redistribute it andor\r\n * modify it under the terms of the GNU General Public License\r\n * as published by the Free Software Foundation; either version 2\r\n * of the License, or (at your option) any later version.\r\n * \r\n * This program is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n * GNU General Public License for more details.\r\n */\r\n\r\n${filecomment}\r\n${package_declaration}\r\n\r\n${typecomment}\r\n${type_declaration}</template><template autoinsert\="true" context\="classbody_context" deleted\="false" description\="Code in new class type bodies" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.classbody" name\="classbody">\r\n</template><template autoinsert\="true" context\="interfacebody_context" deleted\="false" description\="Code in new interface type bodies" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.interfacebody" name\="interfacebody">\r\n</template><template autoinsert\="true" context\="enumbody_context" deleted\="false" description\="Code in new enum type bodies" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.enumbody" name\="enumbody">\r\n</template><template autoinsert\="true" context\="annotationbody_context" deleted\="false" description\="Code in new annotation type bodies" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.annotationbody" name\="annotationbody">\r\n</template><template autoinsert\="true" context\="catchblock_context" deleted\="false" description\="Code in new catch blocks" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.catchblock" name\="catchblock">// ${todo} Auto-generated catch block\r\n${exception_var}.printStackTrace();</template><template autoinsert\="true" context\="methodbody_context" deleted\="false" description\="Code in created method stubs" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.methodbody" name\="methodbody">// ${todo} Auto-generated method stub\r\n${body_statement}</template><template autoinsert\="true" context\="constructorbody_context" deleted\="false" description\="Code in created constructor stubs" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.constructorbody" name\="constructorbody">${body_statement}\r\n// ${todo} Auto-generated constructor stub</template><template autoinsert\="true" context\="getterbody_context" deleted\="false" description\="Code in created getters" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.getterbody" name\="getterbody">return ${field};</template><template autoinsert\="true" context\="setterbody_context" deleted\="false" description\="Code in created setters" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.setterbody" name\="setterbody">${field} \= ${param};</template><template autoinsert\="true" context\="gettercomment_context" deleted\="false" description\="Comment for getter method" enabled\="true" id\="descent.ui.text.codetemplates.gettercomment" name\="gettercomment">/**\r\n * Returns\: the ${bare_field_name}\r\n */</template><template autoinsert\="true" context\="settercomment_context" deleted\="false" description\="Comment for setter method" enabled\="true" id\="descent.ui.text.codetemplates.settercomment" name\="settercomment">/**\r\n * Params\:\r\n *     ${param} \= the ${bare_field_name} to set\r\n */</template><template autoinsert\="true" context\="constructorcomment_context" deleted\="false" description\="Comment for created constructors" enabled\="true" id\="descent.ui.text.codetemplates.constructorcomment" name\="constructorcomment">/**\r\n * ${tags}\r\n */</template><template autoinsert\="true" context\="filecomment_context" deleted\="false" description\="Comment for created Java files" enabled\="true" id\="descent.ui.text.codetemplates.filecomment" name\="filecomment">/**\r\n * \r\n */</template><template autoinsert\="true" context\="typecomment_context" deleted\="false" description\="Comment for created types" enabled\="true" id\="descent.ui.text.codetemplates.typecomment" name\="typecomment">/**\r\n * Authors\: ${user}\r\n */</template><template autoinsert\="true" context\="fieldcomment_context" deleted\="false" description\="Comment for fields" enabled\="true" id\="descent.ui.text.codetemplates.fieldcomment" name\="fieldcomment">/**\r\n * \r\n */</template><template autoinsert\="true" context\="methodcomment_context" deleted\="false" description\="Comment for non-overriding methods" enabled\="true" id\="descent.ui.text.codetemplates.methodcomment" name\="methodcomment">/**\r\n * ${tags}\r\n */</template><template autoinsert\="true" context\="overridecomment_context" deleted\="false" description\="Comment for overriding methods" enabled\="true" id\="descent.ui.text.codetemplates.overridecomment" name\="overridecomment">/* (non-Javadoc)\r\n * ${see_to_overridden}\r\n */</template><template autoinsert\="true" context\="delegatecomment_context" deleted\="false" description\="Comment for delegate methods" enabled\="true" id\="descent.ui.text.codetemplates.delegatecomment" name\="delegatecomment">/**\r\n * ${tags}\r\n * ${see_to_target}\r\n */</template><template autoinsert\="true" context\="classbody_context" deleted\="false" description\="Code in new class type bodies" enabled\="true" id\="descent.ui.text.codetemplates.classbody" name\="classbody">\r\n</template><template autoinsert\="true" context\="interfacebody_context" deleted\="false" description\="Code in new interface type bodies" enabled\="true" id\="descent.ui.text.codetemplates.interfacebody" name\="interfacebody">\r\n</template><template autoinsert\="true" context\="enumbody_context" deleted\="false" description\="Code in new enum type bodies" enabled\="true" id\="descent.ui.text.codetemplates.enumbody" name\="enumbody">\r\n</template><template autoinsert\="true" context\="annotationbody_context" deleted\="false" description\="Code in new annotation type bodies" enabled\="true" id\="descent.ui.text.codetemplates.annotationbody" name\="annotationbody">\r\n</template><template autoinsert\="true" context\="catchblock_context" deleted\="false" description\="Code in new catch blocks" enabled\="true" id\="descent.ui.text.codetemplates.catchblock" name\="catchblock">// ${todo} Auto-generated catch block\r\n${exception_var}.printStackTrace();</template><template autoinsert\="true" context\="methodbody_context" deleted\="false" description\="Code in created method stubs" enabled\="true" id\="descent.ui.text.codetemplates.methodbody" name\="methodbody">// ${todo} Auto-generated method stub\r\n${body_statement}</template><template autoinsert\="true" context\="constructorbody_context" deleted\="false" description\="Code in created constructor stubs" enabled\="true" id\="descent.ui.text.codetemplates.constructorbody" name\="constructorbody">${body_statement}\r\n// ${todo} Auto-generated constructor stub</template><template autoinsert\="true" context\="getterbody_context" deleted\="false" description\="Code in created getters" enabled\="true" id\="descent.ui.text.codetemplates.getterbody" name\="getterbody">return ${field};</template><template autoinsert\="true" context\="setterbody_context" deleted\="false" description\="Code in created setters" enabled\="true" id\="descent.ui.text.codetemplates.setterbody" name\="setterbody">${field} \= ${param};</template></templates>
 eclipse.preferences.version=1
 formatter_profile=_hoofbaby
 formatter_settings_version=1
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ref/codec_adhoc.d	Wed Jul 08 07:29:48 2009 -0700
@@ -0,0 +1,305 @@
+/**
+ * Hoofbaby -- http://www.dsource.org/projects/hoofbaby
+ * Copyright (C) 2009 Robert Fraser
+ * 
+ * This program is free software; you can redistribute it andor
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+module hoofbaby.app.codec_adhoc;
+
+import hoofbaby.util.buffer;
+import tango.stdc.stdio;
+import tango.stdc.stringz;
+import Math = tango.math.Math;
+import tango.io.device.File;
+import tango.time.StopWatch;
+
+import hoofbaby.codec.libav.avutil;
+import hoofbaby.codec.libav.avcodec;
+import hoofbaby.codec.libav.avformat;
+import hoofbaby.codec.libav.avbuffer;
+
+private const int STREAM_FRAME_RATE = 25;
+private const double STREAM_DURATION = 5.0;
+private const int STREAM_NB_FRAMES = (cast(int) (STREAM_DURATION * STREAM_FRAME_RATE));
+private const int OUTBUF_SIZE = 100000;
+
+int codec_main()
+{
+	StopWatch sw;
+	int frameCount = 0;
+	double audioCount, audioIncr, audioIncr2;
+	char* voutbuf, aoutbuf;
+	int res;
+
+	AVFrame* allocFrame(int pix_fmt, int width, int height)
+	{
+		AVFrame* picture;
+		char* buf;
+		int size;
+
+		picture = avcodec_alloc_frame();
+		if(!picture)
+			return null;
+		size = avpicture_get_size(pix_fmt, width, height);
+		buf = cast(char*) av_malloc(size);
+		if(!buf)
+		{
+			av_free(picture);
+			return null;
+		}
+		avpicture_fill(cast(AVPicture*) picture, buf, pix_fmt, width, height);
+		return picture;
+	}
+
+	void generatePicture(AVFrame* pict, int width, int height)
+	{
+		int x, y, i;
+		i = frameCount;
+
+		/* Y */
+		for(y = 0; y < height; y++)
+		{
+			for(x = 0; x < width; x++)
+			{
+				pict.data[0][y * pict.linesize[0] + x] = x + y + i * 3;
+			}
+		}
+
+		/* Cb and Cr */
+		for(y = 0; y < height / 2; y++)
+		{
+			for(x = 0; x < width / 2; x++)
+			{
+				pict.data[1][y * pict.linesize[1] + x] = 128 + y + i * 2;
+				pict.data[2][y * pict.linesize[2] + x] = 64 + x + i * 5;
+			}
+		}
+	}
+
+	int writeVideoFrame(AVFormatContext* ctx, AVStream* stream, AVFrame* picture)
+	{
+		AVCodecContext* vcodec = stream.codec;
+		int ret = 0;
+
+		if(frameCount >= STREAM_NB_FRAMES)
+		{
+			// no more frame to compress. The codec has a latency of a few
+			// frames if using B frames, so we get the last frames by
+			// passing the same picture again
+		}
+		else
+		{
+			generatePicture(picture, vcodec.width, vcodec.height);
+		}
+
+		// Encode it!
+		int outSize = avcodec_encode_video(vcodec, voutbuf, OUTBUF_SIZE, picture);
+		// if zero size, it means the image was buffered.. if not, write that ****!
+		if(outSize > 0)
+		{
+			AVPacket pkt;
+			av_init_packet(&pkt);
+
+			pkt.pts = av_rescale_q(vcodec.coded_frame.pts, vcodec.time_base, stream.time_base);
+			if(vcodec.coded_frame.key_frame)
+				pkt.flags |= PKT_FLAG_KEY;
+			pkt.stream_index = stream.index;
+			pkt.data = voutbuf;
+			pkt.size = outSize;
+
+			// oh yeah!
+			ret = av_write_frame(ctx, &pkt);
+		}
+		else
+		{
+			ret = 0;
+		}
+		
+		frameCount++;
+		assert(!ret, "Error writing video frame");
+		return ret;
+	}
+	
+	int writeAudioFrame(AVFormatContext* ctx, AVStream* stream, short* samples)
+	{
+		AVCodecContext* acodec = stream.codec;
+
+		{
+			int j, i, v;
+		    short *q;
+
+		    q = samples;
+		    for(j = 0; j < acodec.frame_size; j++)
+		    {
+		        v = cast(int)(Math.sin(audioCount) * 10000);
+		        for(i = 0; i < 2; i++) // 2 is number of channels
+		            *q++ = v;
+		        audioCount += audioIncr;
+		        audioIncr += audioIncr2;
+		    }
+		}
+		
+		AVPacket pkt;
+		av_init_packet(&pkt);
+		pkt.size = avcodec_encode_audio(acodec, aoutbuf, OUTBUF_SIZE, samples);
+		//pkt.pts = av_rescale_q(acodec.coded_frame.pts, acodec.time_base, acodec.time_base);
+		pkt.flags |= PKT_FLAG_KEY;
+		pkt.stream_index = stream.index;
+		pkt.data = aoutbuf;
+		
+		int res = av_write_frame(ctx, &pkt) != 0;
+		assert(res == 0, "Error writing audio frame");
+		return res;
+	}
+	
+	//--------------------------------------------------------------------------
+	// Container format
+	
+	sw.start();
+	
+	AVOutputFormat* fmt = guess_format("asf", null, null);
+	assert(fmt !is null, "Could not find format");
+	
+	AVFormatContext* ctx = av_alloc_format_context();
+	assert(ctx !is null, "Could not allocate format context");
+	scope(exit) if(ctx) av_free(ctx);
+	ctx.oformat = fmt;
+	//ctx.preload = cast(int) (0.5 * AV_TIME_BASE);
+	ctx.max_delay = cast(int) (0.7 * AV_TIME_BASE);
+	ctx.loop_output = AVFMT_NOOUTPUTLOOP;
+	ctx.flags |= AVFMT_FLAG_NONBLOCK;
+	
+	AVFormatParameters params;
+	params.prealloced_context = 1;
+	params.video_codec_id = CODEC_ID_WMV2;
+	params.audio_codec_id = CODEC_ID_WMAV2;
+	params.width = 352;
+	params.height = 288;
+	params.time_base.num = 1;
+	params.time_base.den = STREAM_FRAME_RATE;
+	params.pix_fmt = PIX_FMT_YUV420P;
+	params.channels = 2;
+	params.sample_rate = 44100;
+	res = av_set_parameters(ctx, null);
+	assert(res >= 0, "Could not set parameters");
+	
+	//--------------------------------------------------------------------------
+	// Video stream
+	
+	AVStream* vstream = av_new_stream(ctx, 0);
+	assert(vstream !is null, "Could not allocate video stream");
+	ctx.streams[0] = vstream;
+	
+	AVCodec* vcodecName = avcodec_find_encoder(CODEC_ID_WMV2);
+	assert(vcodecName, "Could not find video codec");
+	AVCodecContext* vcodec = vstream.codec;
+	vcodec.codec_id = CODEC_ID_WMV2;
+	vcodec.codec_type = CODEC_TYPE_VIDEO;
+	vcodec.bit_rate = 400000;
+	vcodec.width = 352;
+	vcodec.height = 288;
+	vcodec.gop_size = 12;
+	vcodec.qmin = 3;
+	vcodec.time_base.den = STREAM_FRAME_RATE;
+	vcodec.time_base.num = 1;
+	vcodec.pix_fmt = PIX_FMT_YUV420P;
+	vcodec.flags |= CODEC_FLAG_GLOBAL_HEADER;
+	res = avcodec_open(vcodec, vcodecName);
+	assert(res >= 0, "Could not open video codec");
+	
+	//--------------------------------------------------------------------------
+	// Audio stream
+	
+	AVStream* astream = av_new_stream(ctx, 0);
+	assert(astream !is null, "Could not allocate audio stream");
+	ctx.streams[1] = astream;
+	
+	AVCodec* acodecName = avcodec_find_encoder(CODEC_ID_WMAV2);
+	assert(acodecName, "Could not find audio codec");
+	AVCodecContext* acodec = astream.codec;
+	acodec.codec_id = CODEC_ID_WMAV2;
+	acodec.codec_type = CODEC_TYPE_AUDIO;
+	acodec.bit_rate = 64000;
+	acodec.sample_rate = 44100;
+	acodec.channels = 2;
+	acodec.flags |= CODEC_FLAG_GLOBAL_HEADER;
+	audioCount = 0.0;
+	audioIncr = 2 * Math.PI * 110.0 / acodec.sample_rate;
+	audioIncr2 = 2 * Math.PI * 110.0 / acodec.sample_rate / acodec.sample_rate;
+	res = avcodec_open(acodec, acodecName);
+	assert(res >= 0, "Could not open audio codec");
+	
+	//--------------------------------------------------------------------------
+	// Actually doing stuff
+	
+	// Open output file
+	RingBuffer ringBuf = RingBuffer(1 << 24); // 16 MB
+	scope(exit) ringBuf.free();
+	ctx.pb = getBioContext(&ringBuf, 1 << 24);
+	assert(ctx.pb !is null);
+	assert(ctx.pb.opaque !is null);
+
+	// Allocate a video frame and audio buffer to store stuff
+	AVFrame* frame = allocFrame(PIX_FMT_YUV420P, vcodec.width, vcodec.height);
+	assert(frame !is null, "Could not allocate frame");
+	scope(exit) if(frame) av_free(frame);
+	short* samples = cast(short*) av_malloc(acodec.frame_size * 2 * acodec.channels);
+	assert(frame !is null, "Could not allocate samples");
+	scope(exit) if(samples) av_free(samples);
+	
+	// Allocate some output buffers
+	voutbuf = cast(char*) av_malloc(OUTBUF_SIZE);
+	assert(voutbuf !is null, "Could not allocate video output buffer");
+	scope(exit) if(voutbuf) av_free(voutbuf);
+	aoutbuf = cast(char*) av_malloc(OUTBUF_SIZE);
+	assert(aoutbuf !is null, "Could not allocate audio output buffer");
+	scope(exit) if(aoutbuf) av_free(voutbuf);
+	
+	printf("Setup time %f\n", sw.stop());
+	sw.start();
+	
+	// Write the header
+	res = av_write_header(ctx);
+	assert(res >= 0, "Could not write header for output file (incorrect codec paramters?)");
+	
+    while(true)
+	{
+		double audio_pts = cast(double) astream.pts.val * astream.time_base.num / astream.time_base.den;
+		double video_pts = cast(double) vstream.pts.val * vstream.time_base.num / vstream.time_base.den;
+
+		if(audio_pts >= STREAM_DURATION && video_pts >= STREAM_DURATION)
+			break;
+
+		// Write interleaved audio & video
+		if(audio_pts < video_pts)
+			writeAudioFrame(ctx, astream, samples);
+		else
+			writeVideoFrame(ctx, vstream, frame);
+	}
+    
+	res = av_write_trailer(ctx);
+	assert(res >= 0, "Could not write trailer for output file");
+	
+	printf("Encoding time %f\n", sw.stop());
+	sw.start();
+	
+	scope File file = new File("biff_happy.wmv", File.WriteCreate);
+	uint available = ringBuf.available;
+	auto addr = ringBuf.beginRead(available);
+	file.write(addr[0 .. available]);
+	ringBuf.endRead(available);
+	file.close();
+	
+	printf("IO time %f\n", sw.stop());
+	sw.start();
+	
+	return 0;
+}
--- a/src/build/frankenbuild.d	Tue Jul 07 20:23:49 2009 -0700
+++ b/src/build/frankenbuild.d	Wed Jul 08 07:29:48 2009 -0700
@@ -60,6 +60,21 @@
         cats ~= "debug";
         cats ~= "release";
     }
+    
+    if(cats.contains("transcode"))
+    {
+    	targetFound = true;
+	    cats ~= "platif-debug";
+	    char[] cargs = cbase.dup;
+	    cargs ~= " -g";
+	    cargs ~= " -debug -debug=AVBuffer";
+	    cargs ~= " -ofhoofbaby-d/transcode.exe";
+	    cargs ~= " -I../src/test";
+	    cargs ~= " -oqobjs/debug";
+	    cargs ~= " ../src/test/hoofbaby/test/adhoc/transcode.d";
+	    commands ~= { return cpp("../src/platif/platif.h", "../src/impl/hoofbaby/platinum/platif.d"); };
+	    commands ~= { return rebuild(cargs); };
+    }
 	
 	if(cats.contains("debug"))
 	{
--- a/src/impl/hoofbaby/app/codec_adhoc.d	Tue Jul 07 20:23:49 2009 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,305 +0,0 @@
-/**
- * Hoofbaby -- http://www.dsource.org/projects/hoofbaby
- * Copyright (C) 2009 Robert Fraser
- * 
- * This program is free software; you can redistribute it andor
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
- * of the License, or (at your option) any later version.
- * 
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- */
-module hoofbaby.app.codec_adhoc;
-
-import hoofbaby.util.buffer;
-import tango.stdc.stdio;
-import tango.stdc.stringz;
-import Math = tango.math.Math;
-import tango.io.device.File;
-import tango.time.StopWatch;
-
-import hoofbaby.codec.libav.avutil;
-import hoofbaby.codec.libav.avcodec;
-import hoofbaby.codec.libav.avformat;
-import hoofbaby.codec.libav.avbuffer;
-
-private const int STREAM_FRAME_RATE = 25;
-private const double STREAM_DURATION = 5.0;
-private const int STREAM_NB_FRAMES = (cast(int) (STREAM_DURATION * STREAM_FRAME_RATE));
-private const int OUTBUF_SIZE = 100000;
-
-int codec_main()
-{
-	StopWatch sw;
-	int frameCount = 0;
-	double audioCount, audioIncr, audioIncr2;
-	char* voutbuf, aoutbuf;
-	int res;
-
-	AVFrame* allocFrame(int pix_fmt, int width, int height)
-	{
-		AVFrame* picture;
-		char* buf;
-		int size;
-
-		picture = avcodec_alloc_frame();
-		if(!picture)
-			return null;
-		size = avpicture_get_size(pix_fmt, width, height);
-		buf = cast(char*) av_malloc(size);
-		if(!buf)
-		{
-			av_free(picture);
-			return null;
-		}
-		avpicture_fill(cast(AVPicture*) picture, buf, pix_fmt, width, height);
-		return picture;
-	}
-
-	void generatePicture(AVFrame* pict, int width, int height)
-	{
-		int x, y, i;
-		i = frameCount;
-
-		/* Y */
-		for(y = 0; y < height; y++)
-		{
-			for(x = 0; x < width; x++)
-			{
-				pict.data[0][y * pict.linesize[0] + x] = x + y + i * 3;
-			}
-		}
-
-		/* Cb and Cr */
-		for(y = 0; y < height / 2; y++)
-		{
-			for(x = 0; x < width / 2; x++)
-			{
-				pict.data[1][y * pict.linesize[1] + x] = 128 + y + i * 2;
-				pict.data[2][y * pict.linesize[2] + x] = 64 + x + i * 5;
-			}
-		}
-	}
-
-	int writeVideoFrame(AVFormatContext* ctx, AVStream* stream, AVFrame* picture)
-	{
-		AVCodecContext* vcodec = stream.codec;
-		int ret = 0;
-
-		if(frameCount >= STREAM_NB_FRAMES)
-		{
-			// no more frame to compress. The codec has a latency of a few
-			// frames if using B frames, so we get the last frames by
-			// passing the same picture again
-		}
-		else
-		{
-			generatePicture(picture, vcodec.width, vcodec.height);
-		}
-
-		// Encode it!
-		int outSize = avcodec_encode_video(vcodec, voutbuf, OUTBUF_SIZE, picture);
-		// if zero size, it means the image was buffered.. if not, write that ****!
-		if(outSize > 0)
-		{
-			AVPacket pkt;
-			av_init_packet(&pkt);
-
-			pkt.pts = av_rescale_q(vcodec.coded_frame.pts, vcodec.time_base, stream.time_base);
-			if(vcodec.coded_frame.key_frame)
-				pkt.flags |= PKT_FLAG_KEY;
-			pkt.stream_index = stream.index;
-			pkt.data = voutbuf;
-			pkt.size = outSize;
-
-			// oh yeah!
-			ret = av_write_frame(ctx, &pkt);
-		}
-		else
-		{
-			ret = 0;
-		}
-		
-		frameCount++;
-		assert(!ret, "Error writing video frame");
-		return ret;
-	}
-	
-	int writeAudioFrame(AVFormatContext* ctx, AVStream* stream, short* samples)
-	{
-		AVCodecContext* acodec = stream.codec;
-
-		{
-			int j, i, v;
-		    short *q;
-
-		    q = samples;
-		    for(j = 0; j < acodec.frame_size; j++)
-		    {
-		        v = cast(int)(Math.sin(audioCount) * 10000);
-		        for(i = 0; i < 2; i++) // 2 is number of channels
-		            *q++ = v;
-		        audioCount += audioIncr;
-		        audioIncr += audioIncr2;
-		    }
-		}
-		
-		AVPacket pkt;
-		av_init_packet(&pkt);
-		pkt.size = avcodec_encode_audio(acodec, aoutbuf, OUTBUF_SIZE, samples);
-		//pkt.pts = av_rescale_q(acodec.coded_frame.pts, acodec.time_base, acodec.time_base);
-		pkt.flags |= PKT_FLAG_KEY;
-		pkt.stream_index = stream.index;
-		pkt.data = aoutbuf;
-		
-		int res = av_write_frame(ctx, &pkt) != 0;
-		assert(res == 0, "Error writing audio frame");
-		return res;
-	}
-	
-	//--------------------------------------------------------------------------
-	// Container format
-	
-	sw.start();
-	
-	AVOutputFormat* fmt = guess_format("asf", null, null);
-	assert(fmt !is null, "Could not find format");
-	
-	AVFormatContext* ctx = av_alloc_format_context();
-	assert(ctx !is null, "Could not allocate format context");
-	scope(exit) if(ctx) av_free(ctx);
-	ctx.oformat = fmt;
-	//ctx.preload = cast(int) (0.5 * AV_TIME_BASE);
-	ctx.max_delay = cast(int) (0.7 * AV_TIME_BASE);
-	ctx.loop_output = AVFMT_NOOUTPUTLOOP;
-	ctx.flags |= AVFMT_FLAG_NONBLOCK;
-	
-	AVFormatParameters params;
-	params.prealloced_context = 1;
-	params.video_codec_id = CODEC_ID_WMV2;
-	params.audio_codec_id = CODEC_ID_WMAV2;
-	params.width = 352;
-	params.height = 288;
-	params.time_base.num = 1;
-	params.time_base.den = STREAM_FRAME_RATE;
-	params.pix_fmt = PIX_FMT_YUV420P;
-	params.channels = 2;
-	params.sample_rate = 44100;
-	res = av_set_parameters(ctx, null);
-	assert(res >= 0, "Could not set parameters");
-	
-	//--------------------------------------------------------------------------
-	// Video stream
-	
-	AVStream* vstream = av_new_stream(ctx, 0);
-	assert(vstream !is null, "Could not allocate video stream");
-	ctx.streams[0] = vstream;
-	
-	AVCodec* vcodecName = avcodec_find_encoder(CODEC_ID_WMV2);
-	assert(vcodecName, "Could not find video codec");
-	AVCodecContext* vcodec = vstream.codec;
-	vcodec.codec_id = CODEC_ID_WMV2;
-	vcodec.codec_type = CODEC_TYPE_VIDEO;
-	vcodec.bit_rate = 400000;
-	vcodec.width = 352;
-	vcodec.height = 288;
-	vcodec.gop_size = 12;
-	vcodec.qmin = 3;
-	vcodec.time_base.den = STREAM_FRAME_RATE;
-	vcodec.time_base.num = 1;
-	vcodec.pix_fmt = PIX_FMT_YUV420P;
-	vcodec.flags |= CODEC_FLAG_GLOBAL_HEADER;
-	res = avcodec_open(vcodec, vcodecName);
-	assert(res >= 0, "Could not open video codec");
-	
-	//--------------------------------------------------------------------------
-	// Audio stream
-	
-	AVStream* astream = av_new_stream(ctx, 0);
-	assert(astream !is null, "Could not allocate audio stream");
-	ctx.streams[1] = astream;
-	
-	AVCodec* acodecName = avcodec_find_encoder(CODEC_ID_WMAV2);
-	assert(acodecName, "Could not find audio codec");
-	AVCodecContext* acodec = astream.codec;
-	acodec.codec_id = CODEC_ID_WMAV2;
-	acodec.codec_type = CODEC_TYPE_AUDIO;
-	acodec.bit_rate = 64000;
-	acodec.sample_rate = 44100;
-	acodec.channels = 2;
-	acodec.flags |= CODEC_FLAG_GLOBAL_HEADER;
-	audioCount = 0.0;
-	audioIncr = 2 * Math.PI * 110.0 / acodec.sample_rate;
-	audioIncr2 = 2 * Math.PI * 110.0 / acodec.sample_rate / acodec.sample_rate;
-	res = avcodec_open(acodec, acodecName);
-	assert(res >= 0, "Could not open audio codec");
-	
-	//--------------------------------------------------------------------------
-	// Actually doing stuff
-	
-	// Open output file
-	RingBuffer ringBuf = RingBuffer(1 << 24); // 16 MB
-	scope(exit) ringBuf.free();
-	ctx.pb = getBioContext(&ringBuf, 1 << 24);
-	assert(ctx.pb !is null);
-	assert(ctx.pb.opaque !is null);
-
-	// Allocate a video frame and audio buffer to store stuff
-	AVFrame* frame = allocFrame(PIX_FMT_YUV420P, vcodec.width, vcodec.height);
-	assert(frame !is null, "Could not allocate frame");
-	scope(exit) if(frame) av_free(frame);
-	short* samples = cast(short*) av_malloc(acodec.frame_size * 2 * acodec.channels);
-	assert(frame !is null, "Could not allocate samples");
-	scope(exit) if(samples) av_free(samples);
-	
-	// Allocate some output buffers
-	voutbuf = cast(char*) av_malloc(OUTBUF_SIZE);
-	assert(voutbuf !is null, "Could not allocate video output buffer");
-	scope(exit) if(voutbuf) av_free(voutbuf);
-	aoutbuf = cast(char*) av_malloc(OUTBUF_SIZE);
-	assert(aoutbuf !is null, "Could not allocate audio output buffer");
-	scope(exit) if(aoutbuf) av_free(voutbuf);
-	
-	printf("Setup time %f\n", sw.stop());
-	sw.start();
-	
-	// Write the header
-	res = av_write_header(ctx);
-	assert(res >= 0, "Could not write header for output file (incorrect codec paramters?)");
-	
-    while(true)
-	{
-		double audio_pts = cast(double) astream.pts.val * astream.time_base.num / astream.time_base.den;
-		double video_pts = cast(double) vstream.pts.val * vstream.time_base.num / vstream.time_base.den;
-
-		if(audio_pts >= STREAM_DURATION && video_pts >= STREAM_DURATION)
-			break;
-
-		// Write interleaved audio & video
-		if(audio_pts < video_pts)
-			writeAudioFrame(ctx, astream, samples);
-		else
-			writeVideoFrame(ctx, vstream, frame);
-	}
-    
-	res = av_write_trailer(ctx);
-	assert(res >= 0, "Could not write trailer for output file");
-	
-	printf("Encoding time %f\n", sw.stop());
-	sw.start();
-	
-	scope File file = new File("biff_happy.wmv", File.WriteCreate);
-	uint available = ringBuf.available;
-	auto addr = ringBuf.beginRead(available);
-	file.write(addr[0 .. ringBuf.available()]);
-	ringBuf.endRead(available);
-	file.close();
-	
-	printf("IO time %f\n", sw.stop());
-	sw.start();
-	
-	return 0;
-}
--- a/src/impl/hoofbaby/app/main.d	Tue Jul 07 20:23:49 2009 -0700
+++ b/src/impl/hoofbaby/app/main.d	Wed Jul 08 07:29:48 2009 -0700
@@ -22,15 +22,8 @@
 
 import hoofbaby.app.libs;
 
-version = Codec_Adhoc;
-version(Codec_Adhoc)       import hoofbaby.app.codec_adhoc;
-else version(Server_Adhoc) import Platinum = hoofbaby.platinum.platif;
-else                       static assert(false);
-
-
 public int main(char[][] args)
 {
 	initLibs();
-	version(Codec_Adhoc) return codec_main();
-	else                 return Platinum.mimeOnFire(`D:\Media\Videos`);
+	return Platinum.mimeOnFire(`D:\Media\Videos`);
 }
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/test/hoofbaby/test/adhoc/transcode.d	Wed Jul 08 07:29:48 2009 -0700
@@ -0,0 +1,302 @@
+/**
+ * Hoofbaby -- http://www.dsource.org/projects/hoofbaby
+ * Copyright (C) 2009 Robert Fraser
+ * 
+ * This program is free software; you can redistribute it andor
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+module hoofbaby.test.adhoc.transcode;
+
+//Imports for whole program (just link to them)
+import NONE_1 = tango.stdc.stdarg; // Must be linked in to prevent strange linker errors
+debug import NONE_2 = tango.core.stacktrace.TraceExceptions;
+import NONE_3 = hoofbaby.codec.libav.mingw;
+
+import tango.stdc.stdio;
+import tango.stdc.stringz;
+import Math = tango.math.Math;
+import tango.io.device.File;
+
+import hoofbaby.app.libs;
+import hoofbaby.util.buffer;
+
+import hoofbaby.codec.libav.avutil;
+import hoofbaby.codec.libav.avcodec;
+import hoofbaby.codec.libav.avformat;
+import hoofbaby.codec.libav.avbuffer;
+
+private const int STREAM_FRAME_RATE = 25;
+private const double STREAM_DURATION = 5.0;
+private const int STREAM_NB_FRAMES = (cast(int) (STREAM_DURATION * STREAM_FRAME_RATE));
+private const int OUTBUF_SIZE = 100000;
+
+int main(char[][] args)
+{
+	initLibs();
+	
+	int frameCount = 0;
+	double audioCount, audioIncr, audioIncr2;
+	char* voutbuf, aoutbuf;
+	int res;
+
+	AVFrame* allocFrame(int pix_fmt, int width, int height)
+	{
+		AVFrame* picture;
+		char* buf;
+		int size;
+
+		picture = avcodec_alloc_frame();
+		if(!picture)
+			return null;
+		size = avpicture_get_size(pix_fmt, width, height);
+		buf = cast(char*) av_malloc(size);
+		if(!buf)
+		{
+			av_free(picture);
+			return null;
+		}
+		avpicture_fill(cast(AVPicture*) picture, buf, pix_fmt, width, height);
+		return picture;
+	}
+
+	void generatePicture(AVFrame* pict, int width, int height)
+	{
+		int x, y, i;
+		i = frameCount;
+
+		/* Y */
+		for(y = 0; y < height; y++)
+		{
+			for(x = 0; x < width; x++)
+			{
+				pict.data[0][y * pict.linesize[0] + x] = x + y + i * 3;
+			}
+		}
+
+		/* Cb and Cr */
+		for(y = 0; y < height / 2; y++)
+		{
+			for(x = 0; x < width / 2; x++)
+			{
+				pict.data[1][y * pict.linesize[1] + x] = 128 + y + i * 2;
+				pict.data[2][y * pict.linesize[2] + x] = 64 + x + i * 5;
+			}
+		}
+	}
+
+	int writeVideoFrame(AVFormatContext* ctx, AVStream* stream, AVFrame* picture)
+	{
+		AVCodecContext* vcodec = stream.codec;
+		int ret = 0;
+
+		if(frameCount >= STREAM_NB_FRAMES)
+		{
+			// no more frame to compress. The codec has a latency of a few
+			// frames if using B frames, so we get the last frames by
+			// passing the same picture again
+		}
+		else
+		{
+			generatePicture(picture, vcodec.width, vcodec.height);
+		}
+
+		// Encode it!
+		int outSize = avcodec_encode_video(vcodec, voutbuf, OUTBUF_SIZE, picture);
+		// if zero size, it means the image was buffered.. if not, write that ****!
+		if(outSize > 0)
+		{
+			AVPacket pkt;
+			av_init_packet(&pkt);
+
+			pkt.pts = av_rescale_q(vcodec.coded_frame.pts, vcodec.time_base, stream.time_base);
+			if(vcodec.coded_frame.key_frame)
+				pkt.flags |= PKT_FLAG_KEY;
+			pkt.stream_index = stream.index;
+			pkt.data = voutbuf;
+			pkt.size = outSize;
+
+			// oh yeah!
+			ret = av_write_frame(ctx, &pkt);
+		}
+		else
+		{
+			ret = 0;
+		}
+		
+		frameCount++;
+		assert(!ret, "Error writing video frame");
+		return ret;
+	}
+	
+	int writeAudioFrame(AVFormatContext* ctx, AVStream* stream, short* samples)
+	{
+		AVCodecContext* acodec = stream.codec;
+
+		{
+			int j, i, v;
+		    short *q;
+
+		    q = samples;
+		    for(j = 0; j < acodec.frame_size; j++)
+		    {
+		        v = cast(int)(Math.sin(audioCount) * 10000);
+		        for(i = 0; i < 2; i++) // 2 is number of channels
+		            *q++ = v;
+		        audioCount += audioIncr;
+		        audioIncr += audioIncr2;
+		    }
+		}
+		
+		AVPacket pkt;
+		av_init_packet(&pkt);
+		pkt.size = avcodec_encode_audio(acodec, aoutbuf, OUTBUF_SIZE, samples);
+		//pkt.pts = av_rescale_q(acodec.coded_frame.pts, acodec.time_base, acodec.time_base);
+		pkt.flags |= PKT_FLAG_KEY;
+		pkt.stream_index = stream.index;
+		pkt.data = aoutbuf;
+		
+		int res = av_write_frame(ctx, &pkt) != 0;
+		assert(res == 0, "Error writing audio frame");
+		return res;
+	}
+	
+	//--------------------------------------------------------------------------
+	// Container format
+	
+	AVOutputFormat* fmt = guess_format("asf", null, null);
+	assert(fmt !is null, "Could not find format");
+	
+	AVFormatContext* ctx = av_alloc_format_context();
+	assert(ctx !is null, "Could not allocate format context");
+	scope(exit) if(ctx) av_free(ctx);
+	ctx.oformat = fmt;
+	//ctx.preload = cast(int) (0.5 * AV_TIME_BASE);
+	ctx.max_delay = cast(int) (0.7 * AV_TIME_BASE);
+	ctx.loop_output = AVFMT_NOOUTPUTLOOP;
+	ctx.flags |= AVFMT_FLAG_NONBLOCK;
+	
+	AVFormatParameters params;
+	params.prealloced_context = 1;
+	params.video_codec_id = CODEC_ID_WMV2;
+	params.audio_codec_id = CODEC_ID_WMAV2;
+	params.width = 352;
+	params.height = 288;
+	params.time_base.num = 1;
+	params.time_base.den = STREAM_FRAME_RATE;
+	params.pix_fmt = PIX_FMT_YUV420P;
+	params.channels = 2;
+	params.sample_rate = 44100;
+	res = av_set_parameters(ctx, null);
+	assert(res >= 0, "Could not set parameters");
+	
+	//--------------------------------------------------------------------------
+	// Video stream
+	
+	AVStream* vstream = av_new_stream(ctx, 0);
+	assert(vstream !is null, "Could not allocate video stream");
+	ctx.streams[0] = vstream;
+	
+	AVCodec* vcodecName = avcodec_find_encoder(CODEC_ID_WMV2);
+	assert(vcodecName, "Could not find video codec");
+	AVCodecContext* vcodec = vstream.codec;
+	vcodec.codec_id = CODEC_ID_WMV2;
+	vcodec.codec_type = CODEC_TYPE_VIDEO;
+	vcodec.bit_rate = 400000;
+	vcodec.width = 352;
+	vcodec.height = 288;
+	vcodec.gop_size = 12;
+	vcodec.qmin = 3;
+	vcodec.time_base.den = STREAM_FRAME_RATE;
+	vcodec.time_base.num = 1;
+	vcodec.pix_fmt = PIX_FMT_YUV420P;
+	vcodec.flags |= CODEC_FLAG_GLOBAL_HEADER;
+	res = avcodec_open(vcodec, vcodecName);
+	assert(res >= 0, "Could not open video codec");
+	
+	//--------------------------------------------------------------------------
+	// Audio stream
+	
+	AVStream* astream = av_new_stream(ctx, 0);
+	assert(astream !is null, "Could not allocate audio stream");
+	ctx.streams[1] = astream;
+	
+	AVCodec* acodecName = avcodec_find_encoder(CODEC_ID_WMAV2);
+	assert(acodecName, "Could not find audio codec");
+	AVCodecContext* acodec = astream.codec;
+	acodec.codec_id = CODEC_ID_WMAV2;
+	acodec.codec_type = CODEC_TYPE_AUDIO;
+	acodec.bit_rate = 64000;
+	acodec.sample_rate = 44100;
+	acodec.channels = 2;
+	acodec.flags |= CODEC_FLAG_GLOBAL_HEADER;
+	audioCount = 0.0;
+	audioIncr = 2 * Math.PI * 110.0 / acodec.sample_rate;
+	audioIncr2 = 2 * Math.PI * 110.0 / acodec.sample_rate / acodec.sample_rate;
+	res = avcodec_open(acodec, acodecName);
+	assert(res >= 0, "Could not open audio codec");
+	
+	//--------------------------------------------------------------------------
+	// Actually doing stuff
+	
+	// Open output file
+	RingBuffer ringBuf = RingBuffer(1 << 24); // 16 MB
+	scope(exit) ringBuf.free();
+	ctx.pb = getBioContext(&ringBuf, 1 << 24);
+	assert(ctx.pb !is null);
+	assert(ctx.pb.opaque !is null);
+
+	// Allocate a video frame and audio buffer to store stuff
+	AVFrame* frame = allocFrame(PIX_FMT_YUV420P, vcodec.width, vcodec.height);
+	assert(frame !is null, "Could not allocate frame");
+	scope(exit) if(frame) av_free(frame);
+	short* samples = cast(short*) av_malloc(acodec.frame_size * 2 * acodec.channels);
+	assert(frame !is null, "Could not allocate samples");
+	scope(exit) if(samples) av_free(samples);
+	
+	// Allocate some output buffers
+	voutbuf = cast(char*) av_malloc(OUTBUF_SIZE);
+	assert(voutbuf !is null, "Could not allocate video output buffer");
+	scope(exit) if(voutbuf) av_free(voutbuf);
+	aoutbuf = cast(char*) av_malloc(OUTBUF_SIZE);
+	assert(aoutbuf !is null, "Could not allocate audio output buffer");
+	scope(exit) if(aoutbuf) av_free(voutbuf);
+	
+	// Write the header
+	res = av_write_header(ctx);
+	assert(res >= 0, "Could not write header for output file (incorrect codec paramters?)");
+	
+    while(true)
+	{
+		double audio_pts = cast(double) astream.pts.val * astream.time_base.num / astream.time_base.den;
+		double video_pts = cast(double) vstream.pts.val * vstream.time_base.num / vstream.time_base.den;
+
+		if(audio_pts >= STREAM_DURATION && video_pts >= STREAM_DURATION)
+			break;
+
+		// Write interleaved audio & video
+		if(audio_pts < video_pts)
+			writeAudioFrame(ctx, astream, samples);
+		else
+			writeVideoFrame(ctx, vstream, frame);
+	}
+    
+	res = av_write_trailer(ctx);
+	assert(res >= 0, "Could not write trailer for output file");
+	
+	scope File file = new File("biff_happy.wmv", File.WriteCreate);
+	uint available = ringBuf.available;
+	auto addr = ringBuf.beginRead(available);
+	file.write(addr[0 .. available]);
+	ringBuf.endRead(available);
+	file.close();
+	
+	return 0;
+}
\ No newline at end of file