xref: /haiku/src/apps/mediaplayer/supplier/MediaTrackAudioSupplier.cpp (revision 37c7d5d83a2372a6971e383411d5bacbeef0ebdc)
1 /*
2  * Copyright © 2000-2004 Ingo Weinhold <ingo_weinhold@gmx.de>
3  * Copyright © 2006-2008 Stephan Aßmus <superstippi@gmx.de>
4  * All rights reserved. Distributed under the terms of the MIT License.
5  */
6 #include "MediaTrackAudioSupplier.h"
7 
8 #include <new>
9 #include <algorithm>
10 #include <stdio.h>
11 #include <string.h>
12 
13 #include <MediaFile.h>
14 #include <MediaTrack.h>
15 
16 using namespace std;
17 
18 //#define TRACE_AUDIO_SUPPLIER
19 #ifdef TRACE_AUDIO_SUPPLIER
20 # define TRACE(x...)	printf("MediaTrackAudioSupplier::" x)
21 #else
22 # define TRACE(x...)
23 #endif
24 
25 
26 // #pragma mark - Buffer
27 
28 
29 struct MediaTrackAudioSupplier::Buffer {
30 			void*				data;
31 			int64				offset;
32 			int64				size;
33 			bigtime_t			time_stamp;
34 
35 	static	int					CompareOffset(const void* a, const void* b);
36 };
37 
38 
39 int
40 MediaTrackAudioSupplier::Buffer::CompareOffset(const void* a, const void* b)
41 {
42 	const Buffer* buffer1 = *(const Buffer**)a;
43 	const Buffer* buffer2 = *(const Buffer**)b;
44 	int result = 0;
45 	if (buffer1->offset < buffer2->offset)
46 		result = -1;
47 	else if (buffer1->offset > buffer2->offset)
48 		result = 1;
49 	return result;
50 }
51 
52 
53 // #pragma mark - MediaTrackAudioSupplier
54 
55 
56 MediaTrackAudioSupplier::MediaTrackAudioSupplier(BMediaTrack* mediaTrack,
57 		int32 trackIndex)
58 	: AudioTrackSupplier(),
59 	  fMediaTrack(mediaTrack),
60 	  fBuffer(NULL),
61 	  fBufferOffset(0),
62 	  fBufferSize(0),
63 	  fBuffers(10),
64 	  fHasKeyFrames(false),
65 	  fCountFrames(0),
66 	  fReportSeekError(true),
67 	  fTrackIndex(trackIndex)
68 {
69 	_InitFromTrack();
70 }
71 
72 
73 MediaTrackAudioSupplier::~MediaTrackAudioSupplier()
74 {
75 	_FreeBuffers();
76 	delete[] fBuffer;
77 }
78 
79 
80 const media_format&
81 MediaTrackAudioSupplier::Format() const
82 {
83 	return AudioReader::Format();
84 }
85 
86 
87 status_t
88 MediaTrackAudioSupplier::GetEncodedFormat(media_format* format) const
89 {
90 	if (!fMediaTrack)
91 		return B_NO_INIT;
92 	return fMediaTrack->EncodedFormat(format);
93 }
94 
95 
96 status_t
97 MediaTrackAudioSupplier::GetCodecInfo(media_codec_info* info) const
98 {
99 	if (!fMediaTrack)
100 		return B_NO_INIT;
101 	return fMediaTrack->GetCodecInfo(info);
102 }
103 
104 
105 bigtime_t
106 MediaTrackAudioSupplier::Duration() const
107 {
108 	if (!fMediaTrack)
109 		return 0;
110 
111 	return fMediaTrack->Duration();
112 }
113 
114 
115 // #pragma mark - AudioReader
116 
117 
118 bigtime_t
119 MediaTrackAudioSupplier::InitialLatency() const
120 {
121 	// TODO: this is just a wild guess, and not really founded on anything.
122 	return 100000;
123 }
124 
125 
126 status_t
127 MediaTrackAudioSupplier::Read(void* buffer, int64 pos, int64 frames)
128 {
129 	TRACE("Read(%p, %lld, %lld)\n", buffer, pos,
130 		frames);
131 	TRACE("  this: %p, fOutOffset: %lld\n", this, fOutOffset);
132 
133 //printf("MediaTrackAudioSupplier::Read(%p, %lld, %lld)\n", buffer, pos, frames);
134 
135 	status_t error = InitCheck();
136 	if (error != B_OK) {
137 		TRACE("Read() InitCheck failed\n");
138 		return error;
139 	}
140 
141 	// convert pos according to our offset
142 	pos += fOutOffset;
143 	// Fill the frames after the end of the track with silence.
144 	if (fCountFrames > 0 && pos + frames > fCountFrames) {
145 		int64 size = max(0LL, fCountFrames - pos);
146 		ReadSilence(SkipFrames(buffer, size), frames - size);
147 		frames = size;
148 	}
149 
150 	TRACE("  after eliminating the frames after the track end: %p, %lld, %lld\n",
151 		buffer, pos, frames);
152 
153 #if 0
154 	const media_format& format = Format();
155 	int64 size = format.u.raw_audio.buffer_size;
156 	uint32 bytesPerFrame = format.u.raw_audio.channel_count
157 		* (format.u.raw_audio.format
158 			& media_raw_audio_format::B_AUDIO_SIZE_MASK);
159 	uint32 framesPerBuffer = size / bytesPerFrame;
160 
161 	if (fMediaTrack->CurrentFrame() != pos) {
162 printf("  needing to seek: %lld (%lld)\n", pos,
163 	fMediaTrack->CurrentFrame());
164 
165 		int64 keyFrame = pos;
166 		error = fMediaTrack->FindKeyFrameForFrame(&keyFrame,
167 			B_MEDIA_SEEK_CLOSEST_BACKWARD);
168 		if (error == B_OK) {
169 			error = fMediaTrack->SeekToFrame(&keyFrame,
170 				B_MEDIA_SEEK_CLOSEST_BACKWARD);
171 		}
172 		if (error != B_OK) {
173 printf("  error seeking to position: %lld (%lld)\n", pos,
174 	fMediaTrack->CurrentFrame());
175 
176 			return error;
177 		}
178 
179 		if (keyFrame < pos) {
180 printf("  need to skip %lld frames\n", pos - keyFrame);
181 			uint8 dummyBuffer[size];
182 			while (pos - keyFrame >= framesPerBuffer) {
183 printf("  skipped %lu frames (full buffer)\n", framesPerBuffer);
184 				int64 sizeToRead = size;
185 				fMediaTrack->ReadFrames(dummyBuffer, &sizeToRead);
186 				keyFrame += framesPerBuffer;
187 			}
188 			int64 restSize = pos - keyFrame;
189 			if (restSize > 0) {
190 printf("  skipped %lu frames (rest)\n", framesPerBuffer);
191 				fMediaTrack->ReadFrames(dummyBuffer, &restSize);
192 			}
193 		}
194 	}
195 	while (frames > 0) {
196 printf("  reading %lu frames (full buffer)\n", framesPerBuffer);
197 		int64 sizeToRead = min_c(size, frames * bytesPerFrame);
198 		fMediaTrack->ReadFrames(buffer, &sizeToRead);
199 		buffer = (uint8*)buffer + sizeToRead;
200 		frames -= framesPerBuffer;
201 	}
202 printf("  done\n\n");
203 
204 #else
205 	// read the cached frames
206 	bigtime_t time = system_time();
207 	if (frames > 0)
208 		_ReadCachedFrames(buffer, pos, frames, time);
209 
210 	TRACE("  after reading from cache: %p, %lld, %lld\n", buffer, pos, frames);
211 
212 	// read the remaining (uncached) frames
213 	if (frames > 0)
214 		_ReadUncachedFrames(buffer, pos, frames, time);
215 
216 #endif
217 	TRACE("Read() done\n");
218 
219 	return B_OK;
220 }
221 
222 // InitCheck
223 status_t
224 MediaTrackAudioSupplier::InitCheck() const
225 {
226 	status_t error = AudioReader::InitCheck();
227 	if (error == B_OK && (!fMediaTrack || !fBuffer))
228 		error = B_NO_INIT;
229 	return error;
230 }
231 
232 // #pragma mark -
233 
234 // _InitFromTrack
235 void
236 MediaTrackAudioSupplier::_InitFromTrack()
237 {
238 	if (fMediaTrack && fMediaTrack->DecodedFormat(&fFormat) == B_OK
239 		&& fFormat.type == B_MEDIA_RAW_AUDIO) {
240 
241 		#ifdef TRACE_AUDIO_SUPPLIER
242 			char formatString[256];
243 			string_for_format(fFormat, formatString, 256);
244 			TRACE("MediaTrackAudioSupplier: format is: %s\n", formatString);
245 		#endif
246 
247 		fBuffer = new (nothrow) char[fFormat.u.raw_audio.buffer_size];
248 		_AllocateBuffers();
249 
250 		// Find out, if the track has key frames: as a heuristic we
251 		// check, if the first and the second frame have the same backward
252 		// key frame.
253 		// Note: It shouldn't harm that much, if we're wrong and the
254 		// track has key frame although we found out that it has not.
255 		int64 keyFrame0 = 0;
256 		int64 keyFrame1 = 1;
257 		fMediaTrack->FindKeyFrameForFrame(&keyFrame0,
258 			B_MEDIA_SEEK_CLOSEST_BACKWARD);
259 		fMediaTrack->FindKeyFrameForFrame(&keyFrame1,
260 			B_MEDIA_SEEK_CLOSEST_BACKWARD);
261 		fHasKeyFrames = (keyFrame0 == keyFrame1);
262 
263 		// get the length of the track
264 		fCountFrames = fMediaTrack->CountFrames();
265 
266 		TRACE("MediaTrackAudioSupplier: keyframes: %d, frame count: %lld\n",
267 			fHasKeyFrames, fCountFrames);
268 		printf("MediaTrackAudioSupplier: keyframes: %d, frame count: %lld\n",
269 			fHasKeyFrames, fCountFrames);
270 	} else
271 		fMediaTrack = NULL;
272 }
273 
274 // _FramesPerBuffer
275 int64
276 MediaTrackAudioSupplier::_FramesPerBuffer() const
277 {
278 	int64 sampleSize = fFormat.u.raw_audio.format
279 		& media_raw_audio_format::B_AUDIO_SIZE_MASK;
280 	int64 frameSize = sampleSize * fFormat.u.raw_audio.channel_count;
281 	return fFormat.u.raw_audio.buffer_size / frameSize;
282 }
283 
284 // _CopyFrames
285 //
286 // Given two buffers starting at different frame offsets, this function
287 // copies /frames/ frames at position /position/ from the source to the
288 // target buffer.
289 // Note that no range checking is done.
290 void
291 MediaTrackAudioSupplier::_CopyFrames(void* source, int64 sourceOffset,
292 							  void* target, int64 targetOffset,
293 							  int64 position, int64 frames) const
294 {
295 	int64 sampleSize = fFormat.u.raw_audio.format
296 					   & media_raw_audio_format::B_AUDIO_SIZE_MASK;
297 	int64 frameSize = sampleSize * fFormat.u.raw_audio.channel_count;
298 	source = (char*)source + frameSize * (position - sourceOffset);
299 	target = (char*)target + frameSize * (position - targetOffset);
300 	memcpy(target, source, frames * frameSize);
301 }
302 
303 // _CopyFrames
304 //
305 // Given two buffers starting at different frame offsets, this function
306 // copies /frames/ frames at position /position/ from the source to the
307 // target buffer. This version expects a cache buffer as source.
308 // Note that no range checking is done.
309 void
310 MediaTrackAudioSupplier::_CopyFrames(Buffer* buffer,
311 							  void* target, int64 targetOffset,
312 							  int64 position, int64 frames) const
313 {
314 	_CopyFrames(buffer->data, buffer->offset, target, targetOffset, position,
315 				frames);
316 }
317 
318 // _AllocateBuffers
319 //
320 // Allocates a set of buffers.
321 void
322 MediaTrackAudioSupplier::_AllocateBuffers()
323 {
324 	int32 count = 10;
325 	_FreeBuffers();
326 	int32 bufferSize = fFormat.u.raw_audio.buffer_size;
327 	char* data = new (nothrow) char[bufferSize * count];
328 	for (; count > 0; count--) {
329 		Buffer* buffer = new (nothrow) Buffer;
330 		if (!buffer || !fBuffers.AddItem(buffer)) {
331 			delete buffer;
332 			if (fBuffers.CountItems() == 0)
333 				delete[] data;
334 			return;
335 		}
336 		buffer->data = data;
337 		data += bufferSize;
338 		buffer->offset = 0;
339 		buffer->size = 0;
340 		buffer->time_stamp = 0;
341 	}
342 }
343 
344 // _FreeBuffers
345 //
346 // Frees the allocated buffers.
347 void
348 MediaTrackAudioSupplier::_FreeBuffers()
349 {
350 	if (fBuffers.CountItems() > 0) {
351 		delete[] (char*)_BufferAt(0)->data;
352 		for (int32 i = 0; Buffer* buffer = _BufferAt(i); i++)
353 			delete buffer;
354 		fBuffers.MakeEmpty();
355 	}
356 }
357 
358 // _BufferAt
359 //
360 // Returns the buffer at index /index/.
361 MediaTrackAudioSupplier::Buffer*
362 MediaTrackAudioSupplier::_BufferAt(int32 index) const
363 {
364 	return (Buffer*)fBuffers.ItemAt(index);
365 }
366 
367 // _FindBufferAtFrame
368 //
369 // If any buffer starts at offset /frame/, it is returned, NULL otherwise.
370 MediaTrackAudioSupplier::Buffer*
371 MediaTrackAudioSupplier::_FindBufferAtFrame(int64 frame) const
372 {
373 	Buffer* buffer = NULL;
374 	for (int32 i = 0;
375 		 ((buffer = _BufferAt(i))) && buffer->offset != frame;
376 		 i++);
377 	return buffer;
378 }
379 
380 // _FindUnusedBuffer
381 //
382 // Returns the first unused buffer or NULL if all buffers are used.
383 MediaTrackAudioSupplier::Buffer*
384 MediaTrackAudioSupplier::_FindUnusedBuffer() const
385 {
386 	Buffer* buffer = NULL;
387 	for (int32 i = 0; ((buffer = _BufferAt(i))) && buffer->size != 0; i++);
388 	return buffer;
389 }
390 
391 // _FindUsableBuffer
392 //
393 // Returns either an unused buffer or, if all buffers are used, the least
394 // recently used buffer.
395 // In every case a buffer is returned.
396 MediaTrackAudioSupplier::Buffer*
397 MediaTrackAudioSupplier::_FindUsableBuffer() const
398 {
399 	Buffer* result = _FindUnusedBuffer();
400 	if (!result) {
401 		// find the least recently used buffer.
402 		result = _BufferAt(0);
403 		for (int32 i = 1; Buffer* buffer = _BufferAt(i); i++) {
404 			if (buffer->time_stamp < result->time_stamp)
405 				result = buffer;
406 		}
407 	}
408 	return result;
409 }
410 
411 // _FindUsableBufferFor
412 //
413 // In case there already exists a buffer that starts at position this
414 // one is returned. Otherwise the function returns either an unused
415 // buffer or, if all buffers are used, the least recently used buffer.
416 // In every case a buffer is returned.
417 MediaTrackAudioSupplier::Buffer*
418 MediaTrackAudioSupplier::_FindUsableBufferFor(int64 position) const
419 {
420 	Buffer* buffer = _FindBufferAtFrame(position);
421 	if (!buffer)
422 		buffer = _FindUsableBuffer();
423 	return buffer;
424 }
425 
426 // _GetBuffersFor
427 //
428 // Adds pointers to all buffers to the list that contain data of the
429 // supplied interval.
430 void
431 MediaTrackAudioSupplier::_GetBuffersFor(BList& buffers, int64 position,
432 								 int64 frames) const
433 {
434 	buffers.MakeEmpty();
435 	for (int32 i = 0; Buffer* buffer = _BufferAt(i); i++) {
436 		// Calculate the intersecting interval and add the buffer if it is
437 		// not empty.
438 		int32 startFrame = max(position, buffer->offset);
439 		int32 endFrame = min(position + frames, buffer->offset + buffer->size);
440 		if (startFrame < endFrame)
441 			buffers.AddItem(buffer);
442 	}
443 }
444 
445 // _TouchBuffer
446 //
447 // Sets a buffer's time stamp to the current system time.
448 void
449 MediaTrackAudioSupplier::_TouchBuffer(Buffer* buffer)
450 {
451 	buffer->time_stamp = system_time();
452 }
453 
454 // _ReadBuffer
455 //
456 // Read a buffer from the current position (which is supplied in /position/)
457 // into /buffer/. The buffer's time stamp is set to the current system time.
458 status_t
459 MediaTrackAudioSupplier::_ReadBuffer(Buffer* buffer, int64 position)
460 {
461 	return _ReadBuffer(buffer, position, system_time());
462 }
463 
464 // _ReadBuffer
465 //
466 // Read a buffer from the current position (which is supplied in /position/)
467 // into /buffer/. The buffer's time stamp is set to the supplied time.
468 status_t
469 MediaTrackAudioSupplier::_ReadBuffer(Buffer* buffer, int64 position,
470 	bigtime_t time)
471 {
472 	status_t error = fMediaTrack->ReadFrames(buffer->data, &buffer->size);
473 
474 	TRACE("Read(%p, %lld): %s\n", buffer->data, buffer->size, strerror(error));
475 
476 	buffer->offset = position;
477 	buffer->time_stamp = time;
478 	if (error != B_OK)
479 		buffer->size = 0;
480 	return error;
481 }
482 
483 // _ReadCachedFrames
484 //
485 // Tries to read as much as possible data from the cache. The supplied
486 // buffer pointer as well as position and number of frames are adjusted
487 // accordingly. The used cache buffers are stamped with the supplied
488 // time.
489 void
490 MediaTrackAudioSupplier::_ReadCachedFrames(void*& dest, int64& pos,
491 	int64& frames, bigtime_t time)
492 {
493 	// Get a list of all cache buffers that contain data of the interval,
494 	// and sort it.
495 	BList buffers(10);
496 	_GetBuffersFor(buffers, pos, frames);
497 	buffers.SortItems(Buffer::CompareOffset);
498 	// Step forward through the list of cache buffers and try to read as
499 	// much data from the beginning as possible.
500 	for (int32 i = 0; Buffer* buffer = (Buffer*)buffers.ItemAt(i); i++) {
501 		if (buffer->offset <= pos && buffer->offset + buffer->size > pos) {
502 			// read from the beginning
503 			int64 size = min(frames, buffer->offset + buffer->size - pos);
504 			_CopyFrames(buffer->data, buffer->offset, dest, pos, pos, size);
505 			pos += size;
506 			frames -= size;
507 			dest = SkipFrames(dest, size);
508 			buffer->time_stamp = time;
509 		}
510 	}
511 	// Step backward through the list of cache buffers and try to read as
512 	// much data from the end as possible.
513 	for (int32 i = buffers.CountItems() - 1;
514 		 Buffer* buffer = (Buffer*)buffers.ItemAt(i);
515 		 i++) {
516 		if (buffer->offset < pos + frames
517 			&& buffer->offset + buffer->size >= pos + frames) {
518 			// read from the end
519 			int64 size = min(frames, pos + frames - buffer->offset);
520 			_CopyFrames(buffer->data, buffer->offset, dest, pos,
521 						pos + frames - size, size);
522 			frames -= size;
523 			buffer->time_stamp = time;
524 		}
525 	}
526 }
527 
528 
529 /*!	Reads /frames/ frames from /position/ into /buffer/. The frames are not
530 	read from the cache, but read frames are cached, if possible.
531 	New cache buffers are stamped with the supplied time.
532 	If an error occurs, the untouched part of the buffer is set to 0.
533 */
534 status_t
535 MediaTrackAudioSupplier::_ReadUncachedFrames(void* buffer, int64 position,
536 	int64 frames, bigtime_t time)
537 {
538 	TRACE("_ReadUncachedFrames()\n");
539 	status_t error = B_OK;
540 	// seek to the position
541 	int64 currentPos = position;
542 	if (frames > 0) {
543 		error = _SeekToKeyFrameBackward(currentPos);
544 		TRACE("_ReadUncachedFrames() - seeked to position: %lld\n", currentPos);
545 	}
546 	// read the frames
547 	while (error == B_OK && frames > 0) {
548 		Buffer* cacheBuffer = _FindUsableBufferFor(currentPos);
549 		TRACE("_ReadUncachedFrames() - usable buffer found: %p\n", cacheBuffer);
550 		error = _ReadBuffer(cacheBuffer, currentPos, time);
551 		if (error == B_OK) {
552 			int64 size = min(position + frames,
553 							 cacheBuffer->offset + cacheBuffer->size)
554 						 - position;
555 			if (size > 0) {
556 				_CopyFrames(cacheBuffer, buffer, position, position, size);
557 				buffer = SkipFrames(buffer, size);
558 				position += size;
559 				frames -= size;
560 			}
561 			currentPos += cacheBuffer->size;
562 		}
563 	}
564 
565 #if 1
566 	// Ensure that all frames up to the next key frame are cached.
567 	// This avoids, that each read reaches the BMediaTrack.
568 	if (error == B_OK) {
569 		int64 nextKeyFrame = currentPos;
570 		if (_FindKeyFrameForward(nextKeyFrame) == B_OK) {
571 			while (currentPos < nextKeyFrame) {
572 				// Check, if data at this position are cache.
573 				// If not read it.
574 				Buffer* cacheBuffer = _FindBufferAtFrame(currentPos);
575 				if (!cacheBuffer || cacheBuffer->size == 0) {
576 					cacheBuffer = _FindUsableBufferFor(currentPos);
577 					if (_ReadBuffer(cacheBuffer, currentPos, time) != B_OK)
578 						break;
579 				}
580 				if (cacheBuffer)
581 					currentPos += cacheBuffer->size;
582 			}
583 		}
584 	}
585 #endif
586 
587 	// on error fill up the buffer with silence
588 	if (error != B_OK && frames > 0)
589 		ReadSilence(buffer, frames);
590 	return error;
591 }
592 
593 // _FindKeyFrameForward
594 status_t
595 MediaTrackAudioSupplier::_FindKeyFrameForward(int64& position)
596 {
597 	status_t error = B_OK;
598 #ifdef __HAIKU__
599 	if (fHasKeyFrames) {
600 		error = fMediaTrack->FindKeyFrameForFrame(
601 			&position, B_MEDIA_SEEK_CLOSEST_FORWARD);
602 	} else
603 #endif
604 	{
605 		int64 framesPerBuffer = _FramesPerBuffer();
606 		position += framesPerBuffer - 1;
607 		position = position % framesPerBuffer;
608 	}
609 	return error;
610 }
611 
612 // _FindKeyFrameBackward
613 status_t
614 MediaTrackAudioSupplier::_FindKeyFrameBackward(int64& position)
615 {
616 	status_t error = B_OK;
617 	if (fHasKeyFrames) {
618 		error = fMediaTrack->FindKeyFrameForFrame(
619 			&position, B_MEDIA_SEEK_CLOSEST_BACKWARD);
620 	} else
621 		position -= position % _FramesPerBuffer();
622 	return error;
623 }
624 
625 #if 0
626 // _SeekToKeyFrameForward
627 status_t
628 MediaTrackAudioSupplier::_SeekToKeyFrameForward(int64& position)
629 {
630 	if (position == fMediaTrack->CurrentFrame())
631 		return B_OK;
632 
633 	status_t error = B_OK;
634 	if (fHasKeyFrames) {
635 		#ifdef TRACE_AUDIO_SUPPLIER
636 		int64 oldPosition = position;
637 		#endif
638 		error = fMediaTrack->SeekToFrame(&position,
639 			B_MEDIA_SEEK_CLOSEST_FORWARD);
640 		TRACE("_SeekToKeyFrameForward() - seek to key frame forward: "
641 			"%lld -> %lld (%lld)\n", oldPosition, position,
642 			fMediaTrack->CurrentFrame());
643 	} else {
644 		_FindKeyFrameForward(position);
645 		error = fMediaTrack->SeekToFrame(&position);
646 	}
647 	return error;
648 }
649 #endif
650 
651 // _SeekToKeyFrameBackward
652 status_t
653 MediaTrackAudioSupplier::_SeekToKeyFrameBackward(int64& position)
654 {
655 	if (position == fMediaTrack->CurrentFrame())
656 		return B_OK;
657 
658 	status_t error = B_OK;
659 	if (fHasKeyFrames) {
660 		int64 oldPosition = position;
661 		error = fMediaTrack->FindKeyFrameForFrame(&position,
662 			B_MEDIA_SEEK_CLOSEST_BACKWARD);
663 		if (error == B_OK)
664 			error = fMediaTrack->SeekToFrame(&position, 0);
665 		if (error != B_OK) {
666 			position = fMediaTrack->CurrentFrame();
667 //			if (fReportSeekError) {
668 				printf("  seek to key frame backward: %lld -> %lld (%lld) "
669 					"- %s\n", oldPosition, position,
670 					fMediaTrack->CurrentFrame(), strerror(error));
671 				fReportSeekError = false;
672 //			}
673 		} else {
674 			fReportSeekError = true;
675 		}
676 	} else {
677 		_FindKeyFrameBackward(position);
678 		error = fMediaTrack->SeekToFrame(&position);
679 	}
680 	return error;
681 }
682 
683