2008-05-08 01:09:56 +04:00
|
|
|
/*
|
2007-06-03 00:00:48 +04:00
|
|
|
** Copyright 2004-2007, Marcus Overhagen. All rights reserved.
|
2008-05-08 01:09:56 +04:00
|
|
|
** Copyright 2008, Maurice Kalinowski. All rights reserved.
|
2007-06-03 00:00:48 +04:00
|
|
|
** Distributed under the terms of the MIT License.
|
|
|
|
*/
|
2003-11-24 01:19:21 +03:00
|
|
|
#include "MediaExtractor.h"
|
2003-12-02 02:33:35 +03:00
|
|
|
#include "PluginManager.h"
|
2004-11-21 03:11:39 +03:00
|
|
|
#include "ChunkCache.h"
|
2003-12-05 04:18:51 +03:00
|
|
|
#include "debug.h"
|
2004-01-23 10:11:15 +03:00
|
|
|
|
|
|
|
#include <Autolock.h>
|
|
|
|
|
2003-11-24 02:50:27 +03:00
|
|
|
#include <stdio.h>
|
|
|
|
#include <string.h>
|
2007-06-03 00:00:48 +04:00
|
|
|
#include <new>
|
2003-11-24 02:50:27 +03:00
|
|
|
|
2004-10-24 22:46:07 +04:00
|
|
|
// should be 0, to disable the chunk cache set it to 1
|
2005-06-19 10:01:31 +04:00
|
|
|
#define DISABLE_CHUNK_CACHE 0
|
2004-01-23 10:11:15 +03:00
|
|
|
|
|
|
|
MediaExtractor::MediaExtractor(BDataIO *source, int32 flags)
|
2003-11-24 01:19:21 +03:00
|
|
|
{
|
2003-12-05 04:18:51 +03:00
|
|
|
CALLED();
|
2003-11-24 02:50:27 +03:00
|
|
|
fSource = source;
|
2008-06-06 17:07:02 +04:00
|
|
|
fStreamInfo = NULL;
|
2004-10-24 18:20:01 +04:00
|
|
|
fExtractorThread = -1;
|
|
|
|
fExtractorWaitSem = -1;
|
|
|
|
fTerminateExtractor = false;
|
2004-01-23 10:11:15 +03:00
|
|
|
|
2007-06-03 00:00:48 +04:00
|
|
|
fErr = _plugin_manager.CreateReader(&fReader, &fStreamCount, &fMff, source);
|
2003-12-09 01:16:03 +03:00
|
|
|
if (fErr) {
|
|
|
|
fStreamCount = 0;
|
2007-06-03 00:00:48 +04:00
|
|
|
fReader = NULL;
|
2003-11-24 02:50:27 +03:00
|
|
|
return;
|
2003-12-09 01:16:03 +03:00
|
|
|
}
|
2004-01-23 10:11:15 +03:00
|
|
|
|
2003-11-24 02:50:27 +03:00
|
|
|
fStreamInfo = new stream_info[fStreamCount];
|
2004-01-23 10:11:15 +03:00
|
|
|
|
2003-11-24 02:50:27 +03:00
|
|
|
// initialize stream infos
|
|
|
|
for (int32 i = 0; i < fStreamCount; i++) {
|
|
|
|
fStreamInfo[i].status = B_OK;
|
|
|
|
fStreamInfo[i].cookie = 0;
|
2004-02-29 21:50:23 +03:00
|
|
|
fStreamInfo[i].hasCookie = true;
|
2003-11-24 02:50:27 +03:00
|
|
|
fStreamInfo[i].infoBuffer = 0;
|
|
|
|
fStreamInfo[i].infoBufferSize = 0;
|
2004-10-24 18:20:01 +04:00
|
|
|
fStreamInfo[i].chunkCache = new ChunkCache;
|
2008-03-19 22:39:07 +03:00
|
|
|
memset(&fStreamInfo[i].encodedFormat, 0,
|
|
|
|
sizeof(fStreamInfo[i].encodedFormat));
|
2003-11-24 02:50:27 +03:00
|
|
|
}
|
2004-01-23 10:11:15 +03:00
|
|
|
|
2003-11-24 02:50:27 +03:00
|
|
|
// create all stream cookies
|
|
|
|
for (int32 i = 0; i < fStreamCount; i++) {
|
|
|
|
if (B_OK != fReader->AllocateCookie(i, &fStreamInfo[i].cookie)) {
|
|
|
|
fStreamInfo[i].cookie = 0;
|
2004-02-29 21:50:23 +03:00
|
|
|
fStreamInfo[i].hasCookie = false;
|
2003-11-24 02:50:27 +03:00
|
|
|
fStreamInfo[i].status = B_ERROR;
|
2008-03-19 22:39:07 +03:00
|
|
|
ERROR("MediaExtractor::MediaExtractor: AllocateCookie for stream "
|
|
|
|
"%ld failed\n", i);
|
2003-11-24 02:50:27 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// get info for all streams
|
|
|
|
for (int32 i = 0; i < fStreamCount; i++) {
|
|
|
|
if (fStreamInfo[i].status != B_OK)
|
|
|
|
continue;
|
|
|
|
int64 frameCount;
|
|
|
|
bigtime_t duration;
|
2008-03-19 22:39:07 +03:00
|
|
|
if (B_OK != fReader->GetStreamInfo(fStreamInfo[i].cookie, &frameCount,
|
|
|
|
&duration, &fStreamInfo[i].encodedFormat,
|
|
|
|
&fStreamInfo[i].infoBuffer, &fStreamInfo[i].infoBufferSize)) {
|
2003-11-24 02:50:27 +03:00
|
|
|
fStreamInfo[i].status = B_ERROR;
|
2008-03-19 22:39:07 +03:00
|
|
|
ERROR("MediaExtractor::MediaExtractor: GetStreamInfo for "
|
|
|
|
"stream %ld failed\n", i);
|
2003-11-24 02:50:27 +03:00
|
|
|
}
|
|
|
|
}
|
2008-05-08 01:09:56 +04:00
|
|
|
|
2004-10-24 22:46:07 +04:00
|
|
|
#if DISABLE_CHUNK_CACHE == 0
|
2004-10-24 18:20:01 +04:00
|
|
|
// start extractor thread
|
2004-10-24 21:15:50 +04:00
|
|
|
fExtractorWaitSem = create_sem(1, "media extractor thread sem");
|
2008-03-19 22:39:07 +03:00
|
|
|
fExtractorThread = spawn_thread(extractor_thread, "media extractor thread",
|
|
|
|
10, this);
|
2004-10-24 22:46:07 +04:00
|
|
|
resume_thread(fExtractorThread);
|
|
|
|
#endif
|
2003-11-24 01:19:21 +03:00
|
|
|
}
|
|
|
|
|
2004-10-24 18:20:01 +04:00
|
|
|
|
2003-11-24 01:19:21 +03:00
|
|
|
MediaExtractor::~MediaExtractor()
|
|
|
|
{
|
2003-12-05 04:18:51 +03:00
|
|
|
CALLED();
|
2008-05-08 01:09:56 +04:00
|
|
|
|
2004-10-24 18:20:01 +04:00
|
|
|
// terminate extractor thread
|
|
|
|
fTerminateExtractor = true;
|
|
|
|
release_sem(fExtractorWaitSem);
|
|
|
|
status_t err;
|
|
|
|
wait_for_thread(fExtractorThread, &err);
|
|
|
|
delete_sem(fExtractorWaitSem);
|
2003-12-06 01:11:52 +03:00
|
|
|
|
2003-11-24 02:50:27 +03:00
|
|
|
// free all stream cookies
|
2004-10-24 18:20:01 +04:00
|
|
|
// and chunk caches
|
2003-11-24 02:50:27 +03:00
|
|
|
for (int32 i = 0; i < fStreamCount; i++) {
|
2004-02-29 21:50:23 +03:00
|
|
|
if (fStreamInfo[i].hasCookie)
|
2003-11-24 02:50:27 +03:00
|
|
|
fReader->FreeCookie(fStreamInfo[i].cookie);
|
2004-10-24 18:20:01 +04:00
|
|
|
delete fStreamInfo[i].chunkCache;
|
2003-11-24 02:50:27 +03:00
|
|
|
}
|
|
|
|
|
2007-06-03 00:00:48 +04:00
|
|
|
_plugin_manager.DestroyReader(fReader);
|
2003-12-02 02:33:35 +03:00
|
|
|
|
2003-12-06 01:11:52 +03:00
|
|
|
delete [] fStreamInfo;
|
2003-12-06 01:27:17 +03:00
|
|
|
// fSource is owned by the BMediaFile
|
2003-11-24 01:19:21 +03:00
|
|
|
}
|
|
|
|
|
2004-10-24 18:20:01 +04:00
|
|
|
|
2003-11-24 01:19:21 +03:00
|
|
|
status_t
|
|
|
|
MediaExtractor::InitCheck()
|
|
|
|
{
|
2003-12-05 04:18:51 +03:00
|
|
|
CALLED();
|
2003-11-24 02:50:27 +03:00
|
|
|
return fErr;
|
2003-11-24 01:19:21 +03:00
|
|
|
}
|
|
|
|
|
2004-10-24 18:20:01 +04:00
|
|
|
|
2003-11-24 01:19:21 +03:00
|
|
|
void
|
|
|
|
MediaExtractor::GetFileFormatInfo(media_file_format *mfi) const
|
|
|
|
{
|
2003-12-05 04:18:51 +03:00
|
|
|
CALLED();
|
2003-11-24 02:50:27 +03:00
|
|
|
*mfi = fMff;
|
2003-11-24 01:19:21 +03:00
|
|
|
}
|
|
|
|
|
2004-10-24 18:20:01 +04:00
|
|
|
|
2003-11-24 01:19:21 +03:00
|
|
|
int32
|
|
|
|
MediaExtractor::StreamCount()
|
|
|
|
{
|
2003-12-05 04:18:51 +03:00
|
|
|
CALLED();
|
2003-11-24 02:50:27 +03:00
|
|
|
return fStreamCount;
|
2003-11-24 01:19:21 +03:00
|
|
|
}
|
|
|
|
|
2004-10-24 18:20:01 +04:00
|
|
|
|
2008-05-08 01:09:56 +04:00
|
|
|
const char*
|
|
|
|
MediaExtractor::Copyright()
|
|
|
|
{
|
|
|
|
return fReader->Copyright();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2003-12-07 02:21:23 +03:00
|
|
|
const media_format *
|
2003-11-24 01:19:21 +03:00
|
|
|
MediaExtractor::EncodedFormat(int32 stream)
|
|
|
|
{
|
2003-11-24 02:50:27 +03:00
|
|
|
return &fStreamInfo[stream].encodedFormat;
|
2003-11-24 01:19:21 +03:00
|
|
|
}
|
|
|
|
|
2004-10-24 18:20:01 +04:00
|
|
|
|
2003-11-24 01:19:21 +03:00
|
|
|
int64
|
|
|
|
MediaExtractor::CountFrames(int32 stream) const
|
|
|
|
{
|
2003-12-05 04:18:51 +03:00
|
|
|
CALLED();
|
2003-11-24 02:50:27 +03:00
|
|
|
int64 frameCount;
|
|
|
|
bigtime_t duration;
|
|
|
|
media_format format;
|
2005-06-30 04:24:06 +04:00
|
|
|
const void *infoBuffer;
|
|
|
|
size_t infoSize;
|
2008-05-08 01:09:56 +04:00
|
|
|
|
2008-03-19 22:39:07 +03:00
|
|
|
fReader->GetStreamInfo(fStreamInfo[stream].cookie, &frameCount, &duration,
|
|
|
|
&format, &infoBuffer, &infoSize);
|
2003-11-24 02:50:27 +03:00
|
|
|
|
|
|
|
return frameCount;
|
2003-11-24 01:19:21 +03:00
|
|
|
}
|
|
|
|
|
2004-10-24 18:20:01 +04:00
|
|
|
|
2003-11-24 01:19:21 +03:00
|
|
|
bigtime_t
|
|
|
|
MediaExtractor::Duration(int32 stream) const
|
|
|
|
{
|
2003-12-05 04:18:51 +03:00
|
|
|
CALLED();
|
2003-11-24 02:50:27 +03:00
|
|
|
int64 frameCount;
|
|
|
|
bigtime_t duration;
|
|
|
|
media_format format;
|
2005-06-30 04:24:06 +04:00
|
|
|
const void *infoBuffer;
|
|
|
|
size_t infoSize;
|
2008-05-08 01:09:56 +04:00
|
|
|
|
2008-03-19 22:39:07 +03:00
|
|
|
fReader->GetStreamInfo(fStreamInfo[stream].cookie, &frameCount, &duration,
|
|
|
|
&format, &infoBuffer, &infoSize);
|
2003-11-24 02:50:27 +03:00
|
|
|
|
|
|
|
return duration;
|
2003-11-24 01:19:21 +03:00
|
|
|
}
|
|
|
|
|
2004-10-24 18:20:01 +04:00
|
|
|
|
2003-11-24 01:19:21 +03:00
|
|
|
status_t
|
|
|
|
MediaExtractor::Seek(int32 stream, uint32 seekTo,
|
|
|
|
int64 *frame, bigtime_t *time)
|
|
|
|
{
|
2003-12-05 04:18:51 +03:00
|
|
|
CALLED();
|
2004-11-07 20:04:53 +03:00
|
|
|
if (fStreamInfo[stream].status != B_OK)
|
|
|
|
return fStreamInfo[stream].status;
|
2008-05-08 01:09:56 +04:00
|
|
|
|
2003-11-24 02:50:27 +03:00
|
|
|
status_t result;
|
|
|
|
result = fReader->Seek(fStreamInfo[stream].cookie, seekTo, frame, time);
|
|
|
|
if (result != B_OK)
|
|
|
|
return result;
|
2008-05-08 01:09:56 +04:00
|
|
|
|
2004-10-24 18:20:01 +04:00
|
|
|
// clear buffered chunks
|
|
|
|
fStreamInfo[stream].chunkCache->MakeEmpty();
|
|
|
|
release_sem(fExtractorWaitSem);
|
2008-05-08 01:09:56 +04:00
|
|
|
|
2003-12-05 04:18:51 +03:00
|
|
|
return B_OK;
|
2003-11-24 01:19:21 +03:00
|
|
|
}
|
|
|
|
|
2004-10-24 18:20:01 +04:00
|
|
|
|
2008-03-21 02:36:14 +03:00
|
|
|
status_t
|
|
|
|
MediaExtractor::FindKeyFrame(int32 stream, uint32 seekTo, int64 *frame,
|
|
|
|
bigtime_t *time) const
|
|
|
|
{
|
|
|
|
CALLED();
|
|
|
|
if (fStreamInfo[stream].status != B_OK)
|
|
|
|
return fStreamInfo[stream].status;
|
2008-05-08 01:09:56 +04:00
|
|
|
|
2008-03-21 02:36:14 +03:00
|
|
|
return fReader->FindKeyFrame(fStreamInfo[stream].cookie,
|
|
|
|
seekTo, frame, time);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2003-11-24 01:19:21 +03:00
|
|
|
status_t
|
|
|
|
MediaExtractor::GetNextChunk(int32 stream,
|
2005-06-30 04:24:06 +04:00
|
|
|
const void **chunkBuffer, size_t *chunkSize,
|
2003-11-24 01:19:21 +03:00
|
|
|
media_header *mediaHeader)
|
|
|
|
{
|
2004-11-07 20:04:53 +03:00
|
|
|
if (fStreamInfo[stream].status != B_OK)
|
|
|
|
return fStreamInfo[stream].status;
|
|
|
|
|
2004-10-24 22:46:07 +04:00
|
|
|
#if DISABLE_CHUNK_CACHE > 0
|
|
|
|
static BLocker locker;
|
|
|
|
BAutolock lock(locker);
|
2008-03-19 22:39:07 +03:00
|
|
|
return fReader->GetNextChunk(fStreamInfo[stream].cookie, chunkBuffer,
|
2008-05-08 01:09:56 +04:00
|
|
|
chunkSize, mediaHeader);
|
2004-10-24 22:46:07 +04:00
|
|
|
#endif
|
|
|
|
|
2004-10-24 18:20:01 +04:00
|
|
|
status_t err;
|
2008-03-19 22:39:07 +03:00
|
|
|
err = fStreamInfo[stream].chunkCache->GetNextChunk(chunkBuffer, chunkSize,
|
|
|
|
mediaHeader);
|
2004-10-24 18:20:01 +04:00
|
|
|
release_sem(fExtractorWaitSem);
|
|
|
|
return err;
|
2003-11-24 01:19:21 +03:00
|
|
|
}
|
|
|
|
|
2004-10-24 18:20:01 +04:00
|
|
|
|
2008-03-19 22:39:07 +03:00
|
|
|
class MediaExtractorChunkProvider : public ChunkProvider {
|
2004-01-18 10:37:52 +03:00
|
|
|
private:
|
|
|
|
MediaExtractor * fExtractor;
|
|
|
|
int32 fStream;
|
|
|
|
public:
|
2004-10-23 22:04:50 +04:00
|
|
|
MediaExtractorChunkProvider(MediaExtractor * extractor, int32 stream)
|
|
|
|
{
|
2004-01-18 10:37:52 +03:00
|
|
|
fExtractor = extractor;
|
|
|
|
fStream = stream;
|
|
|
|
}
|
2008-05-08 01:09:56 +04:00
|
|
|
|
2005-06-30 04:24:06 +04:00
|
|
|
virtual status_t GetNextChunk(const void **chunkBuffer, size_t *chunkSize,
|
2004-10-23 22:04:50 +04:00
|
|
|
media_header *mediaHeader)
|
|
|
|
{
|
2008-03-19 22:39:07 +03:00
|
|
|
return fExtractor->GetNextChunk(fStream, chunkBuffer, chunkSize,
|
|
|
|
mediaHeader);
|
2004-01-18 10:37:52 +03:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2004-10-24 18:20:01 +04:00
|
|
|
|
2003-11-24 01:19:21 +03:00
|
|
|
status_t
|
2008-03-19 22:39:07 +03:00
|
|
|
MediaExtractor::CreateDecoder(int32 stream, Decoder **out_decoder,
|
|
|
|
media_codec_info *mci)
|
2003-11-24 02:50:27 +03:00
|
|
|
{
|
2003-12-05 04:18:51 +03:00
|
|
|
CALLED();
|
2003-12-09 01:16:03 +03:00
|
|
|
status_t res;
|
2007-06-03 00:00:48 +04:00
|
|
|
Decoder *decoder;
|
2003-12-09 01:16:03 +03:00
|
|
|
|
|
|
|
res = fStreamInfo[stream].status;
|
|
|
|
if (res != B_OK) {
|
2008-03-19 22:39:07 +03:00
|
|
|
ERROR("MediaExtractor::CreateDecoder can't create decoder for "
|
|
|
|
"stream %ld\n", stream);
|
2003-12-09 01:16:03 +03:00
|
|
|
return res;
|
2003-11-24 02:50:27 +03:00
|
|
|
}
|
2008-05-08 01:09:56 +04:00
|
|
|
|
2008-03-19 22:39:07 +03:00
|
|
|
res = _plugin_manager.CreateDecoder(&decoder,
|
|
|
|
fStreamInfo[stream].encodedFormat);
|
2003-12-09 01:16:03 +03:00
|
|
|
if (res != B_OK) {
|
2008-06-06 17:07:02 +04:00
|
|
|
char formatString[256];
|
|
|
|
string_for_format(&fStreamInfo[stream].encodedFormat, formatString,
|
|
|
|
256);
|
2008-03-19 22:39:07 +03:00
|
|
|
ERROR("MediaExtractor::CreateDecoder _plugin_manager.CreateDecoder "
|
2008-06-06 17:07:02 +04:00
|
|
|
"failed for stream %ld, format: %s\n", stream, formatString);
|
2003-12-09 01:16:03 +03:00
|
|
|
return res;
|
2003-11-24 02:50:27 +03:00
|
|
|
}
|
|
|
|
|
2008-03-19 22:39:07 +03:00
|
|
|
ChunkProvider *chunkProvider
|
|
|
|
= new(std::nothrow) MediaExtractorChunkProvider(this, stream);
|
2007-06-03 00:00:48 +04:00
|
|
|
if (!chunkProvider) {
|
|
|
|
_plugin_manager.DestroyDecoder(decoder);
|
2008-03-19 22:39:07 +03:00
|
|
|
ERROR("MediaExtractor::CreateDecoder can't create chunk provider "
|
|
|
|
"for stream %ld\n", stream);
|
2007-06-03 00:00:48 +04:00
|
|
|
return B_NO_MEMORY;
|
|
|
|
}
|
|
|
|
|
|
|
|
decoder->SetChunkProvider(chunkProvider);
|
2008-05-08 01:09:56 +04:00
|
|
|
|
2008-03-19 22:39:07 +03:00
|
|
|
res = decoder->Setup(&fStreamInfo[stream].encodedFormat,
|
|
|
|
fStreamInfo[stream].infoBuffer, fStreamInfo[stream].infoBufferSize);
|
2003-12-07 02:21:23 +03:00
|
|
|
if (res != B_OK) {
|
2007-06-03 00:00:48 +04:00
|
|
|
_plugin_manager.DestroyDecoder(decoder);
|
2008-03-19 22:39:07 +03:00
|
|
|
ERROR("MediaExtractor::CreateDecoder Setup failed for stream %ld: "
|
|
|
|
"%ld (%s)\n", stream, res, strerror(res));
|
2004-02-16 23:54:35 +03:00
|
|
|
return res;
|
2003-12-07 02:21:23 +03:00
|
|
|
}
|
2004-02-16 23:54:35 +03:00
|
|
|
|
2007-06-03 00:00:48 +04:00
|
|
|
res = _plugin_manager.GetDecoderInfo(decoder, mci);
|
|
|
|
if (res != B_OK) {
|
|
|
|
_plugin_manager.DestroyDecoder(decoder);
|
2008-03-19 22:39:07 +03:00
|
|
|
ERROR("MediaExtractor::CreateDecoder GetCodecInfo failed for stream "
|
|
|
|
"%ld: %ld (%s)\n", stream, res, strerror(res));
|
2007-06-03 00:00:48 +04:00
|
|
|
return res;
|
|
|
|
}
|
2004-10-24 18:20:01 +04:00
|
|
|
|
2007-06-03 00:00:48 +04:00
|
|
|
*out_decoder = decoder;
|
2004-02-16 23:54:35 +03:00
|
|
|
return B_OK;
|
2003-11-24 02:50:27 +03:00
|
|
|
}
|
|
|
|
|
2004-10-24 18:20:01 +04:00
|
|
|
|
|
|
|
int32
|
|
|
|
MediaExtractor::extractor_thread(void *arg)
|
|
|
|
{
|
|
|
|
static_cast<MediaExtractor *>(arg)->ExtractorThread();
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
MediaExtractor::ExtractorThread()
|
|
|
|
{
|
|
|
|
for (;;) {
|
|
|
|
acquire_sem(fExtractorWaitSem);
|
|
|
|
if (fTerminateExtractor)
|
|
|
|
return;
|
2008-05-08 01:09:56 +04:00
|
|
|
|
2004-10-24 18:20:01 +04:00
|
|
|
bool refill_done;
|
|
|
|
do {
|
|
|
|
refill_done = false;
|
|
|
|
for (int32 stream = 0; stream < fStreamCount; stream++) {
|
2004-11-07 20:04:53 +03:00
|
|
|
if (fStreamInfo[stream].status != B_OK)
|
|
|
|
continue;
|
2004-10-24 18:20:01 +04:00
|
|
|
if (fStreamInfo[stream].chunkCache->NeedsRefill()) {
|
|
|
|
media_header mediaHeader;
|
2005-06-30 04:24:06 +04:00
|
|
|
const void *chunkBuffer;
|
|
|
|
size_t chunkSize;
|
2004-10-24 18:20:01 +04:00
|
|
|
status_t err;
|
2008-03-19 22:39:07 +03:00
|
|
|
err = fReader->GetNextChunk(fStreamInfo[stream].cookie,
|
|
|
|
&chunkBuffer, &chunkSize, &mediaHeader);
|
|
|
|
fStreamInfo[stream].chunkCache->PutNextChunk(chunkBuffer,
|
|
|
|
chunkSize, mediaHeader, err);
|
2004-10-24 18:20:01 +04:00
|
|
|
refill_done = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (fTerminateExtractor)
|
|
|
|
return;
|
|
|
|
} while (refill_done);
|
|
|
|
}
|
|
|
|
}
|