xref: /haiku/src/add-ons/media/plugins/ffmpeg/AVFormatWriter.cpp (revision 4384acf6c1a8ab4d4a944354d95ecfcc23e30fc2)
16ac391b3SStephan Aßmus /*
26ac391b3SStephan Aßmus  * Copyright 2009, Stephan Aßmus <superstippi@gmx.de>
36ac391b3SStephan Aßmus  * All rights reserved. Distributed under the terms of the GNU L-GPL license.
46ac391b3SStephan Aßmus  */
56ac391b3SStephan Aßmus 
66ac391b3SStephan Aßmus #include "AVFormatWriter.h"
76ac391b3SStephan Aßmus 
86ac391b3SStephan Aßmus #include <stdio.h>
96ac391b3SStephan Aßmus #include <string.h>
106ac391b3SStephan Aßmus #include <stdlib.h>
116ac391b3SStephan Aßmus 
126ac391b3SStephan Aßmus #include <new>
136ac391b3SStephan Aßmus 
146ac391b3SStephan Aßmus #include <AutoDeleter.h>
156ac391b3SStephan Aßmus #include <Autolock.h>
166ac391b3SStephan Aßmus #include <ByteOrder.h>
176ac391b3SStephan Aßmus #include <DataIO.h>
186ac391b3SStephan Aßmus #include <MediaDefs.h>
196ac391b3SStephan Aßmus #include <MediaFormats.h>
206ac391b3SStephan Aßmus 
216ac391b3SStephan Aßmus extern "C" {
226ac391b3SStephan Aßmus 	#include "avformat.h"
236ac391b3SStephan Aßmus }
246ac391b3SStephan Aßmus 
256ac391b3SStephan Aßmus #include "DemuxerTable.h"
266ac391b3SStephan Aßmus #include "gfx_util.h"
276ac391b3SStephan Aßmus 
286ac391b3SStephan Aßmus 
296ac391b3SStephan Aßmus #define TRACE_AVFORMAT_WRITER
306ac391b3SStephan Aßmus #ifdef TRACE_AVFORMAT_WRITER
316ac391b3SStephan Aßmus #	define TRACE printf
326ac391b3SStephan Aßmus #	define TRACE_IO(a...)
336ac391b3SStephan Aßmus #	define TRACE_PACKET(a...)
346ac391b3SStephan Aßmus #else
356ac391b3SStephan Aßmus #	define TRACE(a...)
366ac391b3SStephan Aßmus #	define TRACE_IO(a...)
376ac391b3SStephan Aßmus #	define TRACE_PACKET(a...)
386ac391b3SStephan Aßmus #endif
396ac391b3SStephan Aßmus 
406ac391b3SStephan Aßmus #define ERROR(a...) fprintf(stderr, a)
416ac391b3SStephan Aßmus 
426ac391b3SStephan Aßmus 
43*4384acf6SStephan Aßmus static const size_t kIOBufferSize = 64 * 1024;
44*4384acf6SStephan Aßmus 	// TODO: This could depend on the BMediaFile creation flags, IIRC,
45*4384acf6SStephan Aßmus 	// they allow to specify a buffering mode.
46*4384acf6SStephan Aßmus 
47*4384acf6SStephan Aßmus 
48*4384acf6SStephan Aßmus // #pragma mark - URLProtocol
49*4384acf6SStephan Aßmus // TODO: Do we need to write an URLProtocol?
50*4384acf6SStephan Aßmus 
51*4384acf6SStephan Aßmus 
526ac391b3SStephan Aßmus // #pragma mark - AVFormatWriter::StreamCookie
536ac391b3SStephan Aßmus 
546ac391b3SStephan Aßmus 
556ac391b3SStephan Aßmus class AVFormatWriter::StreamCookie {
566ac391b3SStephan Aßmus public:
57*4384acf6SStephan Aßmus 								StreamCookie(AVFormatContext* context,
586ac391b3SStephan Aßmus 									BLocker* streamLock);
596ac391b3SStephan Aßmus 	virtual						~StreamCookie();
606ac391b3SStephan Aßmus 
61*4384acf6SStephan Aßmus 			status_t			Init(const media_format* format);
62*4384acf6SStephan Aßmus 
63*4384acf6SStephan Aßmus 			status_t			WriteChunk(const void* chunkBuffer,
64*4384acf6SStephan Aßmus 									size_t chunkSize,
65*4384acf6SStephan Aßmus 									media_encode_info* encodeInfo);
66*4384acf6SStephan Aßmus 
67*4384acf6SStephan Aßmus 			status_t			AddTrackInfo(uint32 code, const void* data,
68*4384acf6SStephan Aßmus 									size_t size, uint32 flags);
69*4384acf6SStephan Aßmus 
706ac391b3SStephan Aßmus private:
71*4384acf6SStephan Aßmus 			AVFormatContext*	fContext;
72*4384acf6SStephan Aßmus 			AVStream*			fStream;
736ac391b3SStephan Aßmus 			// Since different threads may read from the source,
746ac391b3SStephan Aßmus 			// we need to protect the file position and I/O by a lock.
756ac391b3SStephan Aßmus 			BLocker*			fStreamLock;
766ac391b3SStephan Aßmus };
776ac391b3SStephan Aßmus 
786ac391b3SStephan Aßmus 
796ac391b3SStephan Aßmus 
80*4384acf6SStephan Aßmus AVFormatWriter::StreamCookie::StreamCookie(AVFormatContext* context,
816ac391b3SStephan Aßmus 		BLocker* streamLock)
826ac391b3SStephan Aßmus 	:
83*4384acf6SStephan Aßmus 	fContext(context),
84*4384acf6SStephan Aßmus 	fStream(NULL),
856ac391b3SStephan Aßmus 	fStreamLock(streamLock)
866ac391b3SStephan Aßmus {
876ac391b3SStephan Aßmus }
886ac391b3SStephan Aßmus 
896ac391b3SStephan Aßmus 
906ac391b3SStephan Aßmus AVFormatWriter::StreamCookie::~StreamCookie()
916ac391b3SStephan Aßmus {
926ac391b3SStephan Aßmus }
936ac391b3SStephan Aßmus 
946ac391b3SStephan Aßmus 
95*4384acf6SStephan Aßmus status_t
96*4384acf6SStephan Aßmus AVFormatWriter::StreamCookie::Init(const media_format* format)
97*4384acf6SStephan Aßmus {
98*4384acf6SStephan Aßmus 	TRACE("AVFormatWriter::StreamCookie::Init()\n");
99*4384acf6SStephan Aßmus 
100*4384acf6SStephan Aßmus 	BAutolock _(fStreamLock);
101*4384acf6SStephan Aßmus 
102*4384acf6SStephan Aßmus 	fStream = av_new_stream(fContext, fContext->nb_streams);
103*4384acf6SStephan Aßmus 
104*4384acf6SStephan Aßmus 	if (fStream == NULL) {
105*4384acf6SStephan Aßmus 		TRACE("  failed to add new stream\n");
106*4384acf6SStephan Aßmus 		return B_ERROR;
107*4384acf6SStephan Aßmus 	}
108*4384acf6SStephan Aßmus 
109*4384acf6SStephan Aßmus 	// TODO: Setup the stream according to the media format...
110*4384acf6SStephan Aßmus 
111*4384acf6SStephan Aßmus 	return B_OK;
112*4384acf6SStephan Aßmus }
113*4384acf6SStephan Aßmus 
114*4384acf6SStephan Aßmus 
115*4384acf6SStephan Aßmus status_t
116*4384acf6SStephan Aßmus AVFormatWriter::StreamCookie::WriteChunk(const void* chunkBuffer,
117*4384acf6SStephan Aßmus 	size_t chunkSize, media_encode_info* encodeInfo)
118*4384acf6SStephan Aßmus {
119*4384acf6SStephan Aßmus 	TRACE("AVFormatWriter::StreamCookie::WriteChunk(%p, %ld)\n",
120*4384acf6SStephan Aßmus 		chunkBuffer, chunkSize);
121*4384acf6SStephan Aßmus 
122*4384acf6SStephan Aßmus 	BAutolock _(fStreamLock);
123*4384acf6SStephan Aßmus 
124*4384acf6SStephan Aßmus 	return B_ERROR;
125*4384acf6SStephan Aßmus }
126*4384acf6SStephan Aßmus 
127*4384acf6SStephan Aßmus 
128*4384acf6SStephan Aßmus status_t
129*4384acf6SStephan Aßmus AVFormatWriter::StreamCookie::AddTrackInfo(uint32 code,
130*4384acf6SStephan Aßmus 	const void* data, size_t size, uint32 flags)
131*4384acf6SStephan Aßmus {
132*4384acf6SStephan Aßmus 	TRACE("AVFormatWriter::StreamCookie::AddTrackInfo(%lu, %p, %ld, %lu)\n",
133*4384acf6SStephan Aßmus 		code, data, size, flags);
134*4384acf6SStephan Aßmus 
135*4384acf6SStephan Aßmus 	BAutolock _(fStreamLock);
136*4384acf6SStephan Aßmus 
137*4384acf6SStephan Aßmus 	return B_NOT_SUPPORTED;
138*4384acf6SStephan Aßmus }
139*4384acf6SStephan Aßmus 
140*4384acf6SStephan Aßmus 
1416ac391b3SStephan Aßmus // #pragma mark - AVFormatWriter
1426ac391b3SStephan Aßmus 
1436ac391b3SStephan Aßmus 
1446ac391b3SStephan Aßmus AVFormatWriter::AVFormatWriter()
1456ac391b3SStephan Aßmus 	:
146*4384acf6SStephan Aßmus 	fContext(avformat_alloc_context()),
147*4384acf6SStephan Aßmus 	fHeaderWritten(false),
148*4384acf6SStephan Aßmus 	fIOBuffer(NULL),
1496ac391b3SStephan Aßmus 	fStreamLock("stream lock")
1506ac391b3SStephan Aßmus {
1516ac391b3SStephan Aßmus 	TRACE("AVFormatWriter::AVFormatWriter\n");
1526ac391b3SStephan Aßmus }
1536ac391b3SStephan Aßmus 
1546ac391b3SStephan Aßmus 
1556ac391b3SStephan Aßmus AVFormatWriter::~AVFormatWriter()
1566ac391b3SStephan Aßmus {
1576ac391b3SStephan Aßmus 	TRACE("AVFormatWriter::~AVFormatWriter\n");
158*4384acf6SStephan Aßmus 
159*4384acf6SStephan Aßmus 	av_free(fContext);
160*4384acf6SStephan Aßmus 
161*4384acf6SStephan Aßmus 	delete[] fIOBuffer;
1626ac391b3SStephan Aßmus }
1636ac391b3SStephan Aßmus 
1646ac391b3SStephan Aßmus 
1656ac391b3SStephan Aßmus // #pragma mark -
1666ac391b3SStephan Aßmus 
1676ac391b3SStephan Aßmus 
1686ac391b3SStephan Aßmus status_t
169*4384acf6SStephan Aßmus AVFormatWriter::Init(const media_file_format* fileFormat)
170*4384acf6SStephan Aßmus {
171*4384acf6SStephan Aßmus 	TRACE("AVFormatWriter::Init()\n");
172*4384acf6SStephan Aßmus 
173*4384acf6SStephan Aßmus 	delete[] fIOBuffer;
174*4384acf6SStephan Aßmus 	fIOBuffer = new(std::nothrow) uint8[kIOBufferSize];
175*4384acf6SStephan Aßmus 	if (fIOBuffer == NULL)
176*4384acf6SStephan Aßmus 		return B_NO_MEMORY;
177*4384acf6SStephan Aßmus 
178*4384acf6SStephan Aßmus 	// Init I/O context with buffer and hook functions, pass ourself as
179*4384acf6SStephan Aßmus 	// cookie.
180*4384acf6SStephan Aßmus 	if (init_put_byte(&fIOContext, fIOBuffer, kIOBufferSize, 0, this,
181*4384acf6SStephan Aßmus 			0, _Write, _Seek) != 0) {
182*4384acf6SStephan Aßmus 		TRACE("  init_put_byte() failed!\n");
183*4384acf6SStephan Aßmus 		return B_ERROR;
184*4384acf6SStephan Aßmus 	}
185*4384acf6SStephan Aßmus 
186*4384acf6SStephan Aßmus 	// TODO: Is this how it works?
187*4384acf6SStephan Aßmus 	// TODO: Is the cookie stored in ByteIOContext? Or does it need to be
188*4384acf6SStephan Aßmus 	// stored in fContext->priv_data?
189*4384acf6SStephan Aßmus 	fContext->pb = &fIOContext;
190*4384acf6SStephan Aßmus 
191*4384acf6SStephan Aßmus 	// TODO: Set the AVOutputFormat according to fileFormat...
192*4384acf6SStephan Aßmus 	fContext->oformat = guess_format(fileFormat->short_name,
193*4384acf6SStephan Aßmus 		fileFormat->file_extension, fileFormat->mime_type);
194*4384acf6SStephan Aßmus 	if (fContext->oformat == NULL) {
195*4384acf6SStephan Aßmus 		TRACE("  failed to find AVOuputFormat for %s\n",
196*4384acf6SStephan Aßmus 			fileFormat->short_name);
197*4384acf6SStephan Aßmus 		return B_NOT_SUPPORTED;
198*4384acf6SStephan Aßmus 	}
199*4384acf6SStephan Aßmus 
200*4384acf6SStephan Aßmus 	TRACE("  found AVOuputFormat for %s: %s\n", fileFormat->short_name,
201*4384acf6SStephan Aßmus 		fContext->oformat->name);
202*4384acf6SStephan Aßmus 
203*4384acf6SStephan Aßmus 	return B_OK;
204*4384acf6SStephan Aßmus }
205*4384acf6SStephan Aßmus 
206*4384acf6SStephan Aßmus 
207*4384acf6SStephan Aßmus status_t
2086ac391b3SStephan Aßmus AVFormatWriter::SetCopyright(const char* copyright)
2096ac391b3SStephan Aßmus {
2106ac391b3SStephan Aßmus 	TRACE("AVFormatWriter::SetCopyright(%s)\n", copyright);
2116ac391b3SStephan Aßmus 
2126ac391b3SStephan Aßmus 	return B_NOT_SUPPORTED;
2136ac391b3SStephan Aßmus }
2146ac391b3SStephan Aßmus 
2156ac391b3SStephan Aßmus 
2166ac391b3SStephan Aßmus status_t
2176ac391b3SStephan Aßmus AVFormatWriter::CommitHeader()
2186ac391b3SStephan Aßmus {
2196ac391b3SStephan Aßmus 	TRACE("AVFormatWriter::CommitHeader\n");
2206ac391b3SStephan Aßmus 
221*4384acf6SStephan Aßmus 	if (fContext == NULL)
222*4384acf6SStephan Aßmus 		return B_NO_INIT;
223*4384acf6SStephan Aßmus 
224*4384acf6SStephan Aßmus 	if (fHeaderWritten)
225*4384acf6SStephan Aßmus 		return B_NOT_ALLOWED;
226*4384acf6SStephan Aßmus 
227*4384acf6SStephan Aßmus 	int result = av_write_header(fContext);
228*4384acf6SStephan Aßmus 	if (result < 0)
229*4384acf6SStephan Aßmus 		TRACE("  av_write_header(): %d\n", result);
230*4384acf6SStephan Aßmus 	else
231*4384acf6SStephan Aßmus 		fHeaderWritten = true;
232*4384acf6SStephan Aßmus 
233*4384acf6SStephan Aßmus 	return result == 0 ? B_OK : B_ERROR;
2346ac391b3SStephan Aßmus }
2356ac391b3SStephan Aßmus 
2366ac391b3SStephan Aßmus 
2376ac391b3SStephan Aßmus status_t
2386ac391b3SStephan Aßmus AVFormatWriter::Flush()
2396ac391b3SStephan Aßmus {
2406ac391b3SStephan Aßmus 	TRACE("AVFormatWriter::Flush\n");
2416ac391b3SStephan Aßmus 
2426ac391b3SStephan Aßmus 	return B_NOT_SUPPORTED;
2436ac391b3SStephan Aßmus }
2446ac391b3SStephan Aßmus 
2456ac391b3SStephan Aßmus 
2466ac391b3SStephan Aßmus status_t
2476ac391b3SStephan Aßmus AVFormatWriter::Close()
2486ac391b3SStephan Aßmus {
2496ac391b3SStephan Aßmus 	TRACE("AVFormatWriter::Close\n");
2506ac391b3SStephan Aßmus 
251*4384acf6SStephan Aßmus 	if (fContext == NULL)
252*4384acf6SStephan Aßmus 		return B_NO_INIT;
253*4384acf6SStephan Aßmus 
254*4384acf6SStephan Aßmus 	if (!fHeaderWritten)
255*4384acf6SStephan Aßmus 		return B_NOT_ALLOWED;
256*4384acf6SStephan Aßmus 
257*4384acf6SStephan Aßmus 	int result = av_write_trailer(fContext);
258*4384acf6SStephan Aßmus 	if (result < 0)
259*4384acf6SStephan Aßmus 		TRACE("  av_write_trailer(): %d\n", result);
260*4384acf6SStephan Aßmus 
261*4384acf6SStephan Aßmus 	return result == 0 ? B_OK : B_ERROR;
2626ac391b3SStephan Aßmus }
2636ac391b3SStephan Aßmus 
2646ac391b3SStephan Aßmus 
2656ac391b3SStephan Aßmus status_t
266*4384acf6SStephan Aßmus AVFormatWriter::AllocateCookie(void** _cookie, const media_format* format)
2676ac391b3SStephan Aßmus {
2686ac391b3SStephan Aßmus 	TRACE("AVFormatWriter::AllocateCookie()\n");
2696ac391b3SStephan Aßmus 
2706ac391b3SStephan Aßmus 	BAutolock _(fStreamLock);
2716ac391b3SStephan Aßmus 
2726ac391b3SStephan Aßmus 	if (_cookie == NULL)
2736ac391b3SStephan Aßmus 		return B_BAD_VALUE;
2746ac391b3SStephan Aßmus 
275*4384acf6SStephan Aßmus 	StreamCookie* cookie = new(std::nothrow) StreamCookie(fContext,
276*4384acf6SStephan Aßmus 		&fStreamLock);
277*4384acf6SStephan Aßmus 
278*4384acf6SStephan Aßmus 	return cookie->Init(format);
2796ac391b3SStephan Aßmus }
2806ac391b3SStephan Aßmus 
2816ac391b3SStephan Aßmus 
2826ac391b3SStephan Aßmus status_t
2836ac391b3SStephan Aßmus AVFormatWriter::FreeCookie(void* _cookie)
2846ac391b3SStephan Aßmus {
2856ac391b3SStephan Aßmus 	BAutolock _(fStreamLock);
2866ac391b3SStephan Aßmus 
2876ac391b3SStephan Aßmus 	StreamCookie* cookie = reinterpret_cast<StreamCookie*>(_cookie);
2886ac391b3SStephan Aßmus 	delete cookie;
2896ac391b3SStephan Aßmus 
2906ac391b3SStephan Aßmus 	return B_OK;
2916ac391b3SStephan Aßmus }
2926ac391b3SStephan Aßmus 
2936ac391b3SStephan Aßmus 
2946ac391b3SStephan Aßmus // #pragma mark -
2956ac391b3SStephan Aßmus 
2966ac391b3SStephan Aßmus 
2976ac391b3SStephan Aßmus status_t
298fa770e4cSStephan Aßmus AVFormatWriter::SetCopyright(void* cookie, const char* copyright)
299fa770e4cSStephan Aßmus {
300fa770e4cSStephan Aßmus 	TRACE("AVFormatWriter::SetCopyright(%p, %s)\n", cookie, copyright);
301fa770e4cSStephan Aßmus 
302fa770e4cSStephan Aßmus 	return B_NOT_SUPPORTED;
303fa770e4cSStephan Aßmus }
304fa770e4cSStephan Aßmus 
305fa770e4cSStephan Aßmus 
306fa770e4cSStephan Aßmus status_t
307*4384acf6SStephan Aßmus AVFormatWriter::AddTrackInfo(void* _cookie, uint32 code,
3086ac391b3SStephan Aßmus 	const void* data, size_t size, uint32 flags)
3096ac391b3SStephan Aßmus {
3106ac391b3SStephan Aßmus 	TRACE("AVFormatWriter::AddTrackInfo(%lu, %p, %ld, %lu)\n",
3116ac391b3SStephan Aßmus 		code, data, size, flags);
3126ac391b3SStephan Aßmus 
313*4384acf6SStephan Aßmus 	StreamCookie* cookie = reinterpret_cast<StreamCookie*>(_cookie);
314*4384acf6SStephan Aßmus 	return cookie->AddTrackInfo(code, data, size, flags);
3156ac391b3SStephan Aßmus }
3166ac391b3SStephan Aßmus 
3176ac391b3SStephan Aßmus 
3186ac391b3SStephan Aßmus status_t
319*4384acf6SStephan Aßmus AVFormatWriter::WriteChunk(void* _cookie, const void* chunkBuffer,
320fa770e4cSStephan Aßmus 	size_t chunkSize, media_encode_info* encodeInfo)
3216ac391b3SStephan Aßmus {
322fa770e4cSStephan Aßmus 	TRACE("AVFormatWriter::WriteChunk(%p, %ld, %p)\n", chunkBuffer, chunkSize,
323fa770e4cSStephan Aßmus 		encodeInfo);
3246ac391b3SStephan Aßmus 
325*4384acf6SStephan Aßmus 	StreamCookie* cookie = reinterpret_cast<StreamCookie*>(_cookie);
326*4384acf6SStephan Aßmus 	return cookie->WriteChunk(chunkBuffer, chunkSize, encodeInfo);
3276ac391b3SStephan Aßmus }
3286ac391b3SStephan Aßmus 
3296ac391b3SStephan Aßmus 
3302e9d65abSStephan Aßmus // #pragma mark -
3312e9d65abSStephan Aßmus 
3322e9d65abSStephan Aßmus 
3332e9d65abSStephan Aßmus /*static*/ int
3342e9d65abSStephan Aßmus AVFormatWriter::_Write(void* cookie, const uint8* buffer, int bufferSize)
3352e9d65abSStephan Aßmus {
3362e9d65abSStephan Aßmus 	TRACE_IO("AVFormatWriter::_Write(%p, %p, %d)\n",
3372e9d65abSStephan Aßmus 		cookie, buffer, bufferSize);
3382e9d65abSStephan Aßmus 
3392e9d65abSStephan Aßmus 	AVFormatWriter* writer = reinterpret_cast<AVFormatWriter*>(cookie);
3402e9d65abSStephan Aßmus 
3412e9d65abSStephan Aßmus 	ssize_t written = writer->fTarget->Write(buffer, bufferSize);
3422e9d65abSStephan Aßmus 
3432e9d65abSStephan Aßmus 	TRACE_IO("  written: %ld\n", written);
3442e9d65abSStephan Aßmus 	return (int)written;
3452e9d65abSStephan Aßmus 
3462e9d65abSStephan Aßmus }
3472e9d65abSStephan Aßmus 
3482e9d65abSStephan Aßmus 
3492e9d65abSStephan Aßmus /*static*/ off_t
3502e9d65abSStephan Aßmus AVFormatWriter::_Seek(void* cookie, off_t offset, int whence)
3512e9d65abSStephan Aßmus {
3522e9d65abSStephan Aßmus 	TRACE_IO("AVFormatWriter::_Seek(%p, %lld, %d)\n",
3532e9d65abSStephan Aßmus 		cookie, offset, whence);
3542e9d65abSStephan Aßmus 
3552e9d65abSStephan Aßmus 	AVFormatWriter* writer = reinterpret_cast<AVFormatWriter*>(cookie);
3562e9d65abSStephan Aßmus 
3572e9d65abSStephan Aßmus 	BPositionIO* positionIO = dynamic_cast<BPositionIO*>(writer->fTarget);
3582e9d65abSStephan Aßmus 	if (positionIO == NULL)
3592e9d65abSStephan Aßmus 		return -1;
3602e9d65abSStephan Aßmus 
361*4384acf6SStephan Aßmus 	// Support for special file size retrieval API without seeking anywhere:
3622e9d65abSStephan Aßmus 	if (whence == AVSEEK_SIZE) {
3632e9d65abSStephan Aßmus 		off_t size;
3642e9d65abSStephan Aßmus 		if (positionIO->GetSize(&size) == B_OK)
3652e9d65abSStephan Aßmus 			return size;
3662e9d65abSStephan Aßmus 		return -1;
3672e9d65abSStephan Aßmus 	}
3682e9d65abSStephan Aßmus 
3692e9d65abSStephan Aßmus 	off_t position = positionIO->Seek(offset, whence);
3702e9d65abSStephan Aßmus 	TRACE_IO("  position: %lld\n", position);
3712e9d65abSStephan Aßmus 	if (position < 0)
3722e9d65abSStephan Aßmus 		return -1;
3732e9d65abSStephan Aßmus 
3742e9d65abSStephan Aßmus 	return position;
3752e9d65abSStephan Aßmus }
3762e9d65abSStephan Aßmus 
3772e9d65abSStephan Aßmus 
378