blob: 1a4bf081dcde97afbbaf43cda9078a0ced493e27 [file] [log] [blame]
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
//#define LOG_NDEBUG 0
#define LOG_TAG "sf2"
#include <inttypes.h>
#include <utils/Log.h>
#include <signal.h>
#include <binder/ProcessState.h>
#include <media/IMediaHTTPService.h>
#include <media/stagefright/foundation/hexdump.h>
#include <media/stagefright/foundation/ABuffer.h>
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/foundation/ALooper.h>
#include <media/stagefright/foundation/AMessage.h>
#include <media/stagefright/ACodec.h>
#include <media/stagefright/DataSource.h>
#include <media/stagefright/MediaBuffer.h>
#include <media/stagefright/MediaDefs.h>
#include <media/stagefright/MediaExtractor.h>
#include <media/stagefright/MediaSource.h>
#include <media/stagefright/MetaData.h>
#include <media/stagefright/Utils.h>
#include <gui/SurfaceComposerClient.h>
#include <gui/Surface.h>
#include "include/ESDS.h"
using namespace android;
volatile static bool ctrlc = false;
static sighandler_t oldhandler = NULL;
static void mysighandler(int signum) {
if (signum == SIGINT) {
ctrlc = true;
return;
}
oldhandler(signum);
}
struct Controller : public AHandler {
Controller(const char *uri, bool decodeAudio,
const sp<Surface> &surface, bool renderToSurface)
: mURI(uri),
mDecodeAudio(decodeAudio),
mSurface(surface),
mRenderToSurface(renderToSurface),
mCodec(new ACodec),
mIsVorbis(false) {
CHECK(!mDecodeAudio || mSurface == NULL);
}
void startAsync() {
(new AMessage(kWhatStart, this))->post();
}
protected:
virtual ~Controller() {
}
virtual void printStatistics() {
int64_t delayUs = ALooper::GetNowUs() - mStartTimeUs;
if (mDecodeAudio) {
printf("%" PRId64 " bytes received. %.2f KB/sec\n",
mTotalBytesReceived,
mTotalBytesReceived * 1E6 / 1024 / delayUs);
} else {
printf("%d frames decoded, %.2f fps. %" PRId64 " bytes "
"received. %.2f KB/sec\n",
mNumOutputBuffersReceived,
mNumOutputBuffersReceived * 1E6 / delayUs,
mTotalBytesReceived,
mTotalBytesReceived * 1E6 / 1024 / delayUs);
}
}
virtual void onMessageReceived(const sp<AMessage> &msg) {
if (ctrlc) {
printf("\n");
printStatistics();
(new AMessage(kWhatStop, this))->post();
ctrlc = false;
}
switch (msg->what()) {
case kWhatStart:
{
#if 1
mDecodeLooper = looper();
#else
mDecodeLooper = new ALooper;
mDecodeLooper->setName("sf2 decode looper");
mDecodeLooper->start();
#endif
sp<DataSource> dataSource =
DataSource::CreateFromURI(
NULL /* httpService */, mURI.c_str());
sp<IMediaExtractor> extractor =
MediaExtractor::Create(dataSource);
for (size_t i = 0; i < extractor->countTracks(); ++i) {
sp<MetaData> meta = extractor->getTrackMetaData(i);
const char *mime;
CHECK(meta->findCString(kKeyMIMEType, &mime));
if (!strncasecmp(mDecodeAudio ? "audio/" : "video/",
mime, 6)) {
mSource = extractor->getTrack(i);
if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_VORBIS)) {
mIsVorbis = true;
} else {
mIsVorbis = false;
}
break;
}
}
if (mSource == NULL) {
printf("no %s track found\n", mDecodeAudio ? "audio" : "video");
exit (1);
}
CHECK_EQ(mSource->start(), (status_t)OK);
mDecodeLooper->registerHandler(mCodec);
mCodec->setNotificationMessage(
new AMessage(kWhatCodecNotify, this));
sp<AMessage> format = makeFormat(mSource->getFormat());
if (mSurface != NULL) {
format->setObject("surface", mSurface);
}
mCodec->initiateSetup(format);
mCSDIndex = 0;
mStartTimeUs = ALooper::GetNowUs();
mNumOutputBuffersReceived = 0;
mTotalBytesReceived = 0;
mLeftOverBuffer = NULL;
mFinalResult = OK;
mSeekState = SEEK_NONE;
// (new AMessage(kWhatSeek, this))->post(5000000ll);
break;
}
case kWhatSeek:
{
printf("+");
fflush(stdout);
CHECK(mSeekState == SEEK_NONE
|| mSeekState == SEEK_FLUSH_COMPLETED);
if (mLeftOverBuffer != NULL) {
mLeftOverBuffer->release();
mLeftOverBuffer = NULL;
}
mSeekState = SEEK_FLUSHING;
mSeekTimeUs = 30000000ll;
mCodec->signalFlush();
break;
}
case kWhatStop:
{
if (mLeftOverBuffer != NULL) {
mLeftOverBuffer->release();
mLeftOverBuffer = NULL;
}
CHECK_EQ(mSource->stop(), (status_t)OK);
mSource.clear();
mCodec->initiateShutdown();
break;
}
case kWhatCodecNotify:
{
int32_t what;
CHECK(msg->findInt32("what", &what));
if (what == CodecBase::kWhatFillThisBuffer) {
onFillThisBuffer(msg);
} else if (what == CodecBase::kWhatDrainThisBuffer) {
if ((mNumOutputBuffersReceived++ % 16) == 0) {
printf(".");
fflush(stdout);
}
onDrainThisBuffer(msg);
} else if (what == CodecBase::kWhatEOS
|| what == CodecBase::kWhatError) {
printf((what == CodecBase::kWhatEOS) ? "$\n" : "E\n");
printStatistics();
(new AMessage(kWhatStop, this))->post();
} else if (what == CodecBase::kWhatFlushCompleted) {
mSeekState = SEEK_FLUSH_COMPLETED;
mCodec->signalResume();
(new AMessage(kWhatSeek, this))->post(5000000ll);
} else if (what == CodecBase::kWhatOutputFormatChanged) {
} else if (what == CodecBase::kWhatShutdownCompleted) {
mDecodeLooper->unregisterHandler(mCodec->id());
if (mDecodeLooper != looper()) {
mDecodeLooper->stop();
}
looper()->stop();
}
break;
}
default:
TRESPASS();
break;
}
}
private:
enum {
kWhatStart = 'strt',
kWhatStop = 'stop',
kWhatCodecNotify = 'noti',
kWhatSeek = 'seek',
};
sp<ALooper> mDecodeLooper;
AString mURI;
bool mDecodeAudio;
sp<Surface> mSurface;
bool mRenderToSurface;
sp<ACodec> mCodec;
sp<IMediaSource> mSource;
bool mIsVorbis;
Vector<sp<ABuffer> > mCSD;
size_t mCSDIndex;
MediaBuffer *mLeftOverBuffer;
status_t mFinalResult;
int64_t mStartTimeUs;
int32_t mNumOutputBuffersReceived;
int64_t mTotalBytesReceived;
enum SeekState {
SEEK_NONE,
SEEK_FLUSHING,
SEEK_FLUSH_COMPLETED,
};
SeekState mSeekState;
int64_t mSeekTimeUs;
sp<AMessage> makeFormat(const sp<MetaData> &meta) {
CHECK(mCSD.isEmpty());
const char *mime;
CHECK(meta->findCString(kKeyMIMEType, &mime));
sp<AMessage> msg = new AMessage;
msg->setString("mime", mime);
if (!strncasecmp("video/", mime, 6)) {
int32_t width, height;
CHECK(meta->findInt32(kKeyWidth, &width));
CHECK(meta->findInt32(kKeyHeight, &height));
msg->setInt32("width", width);
msg->setInt32("height", height);
} else {
CHECK(!strncasecmp("audio/", mime, 6));
int32_t numChannels, sampleRate;
CHECK(meta->findInt32(kKeyChannelCount, &numChannels));
CHECK(meta->findInt32(kKeySampleRate, &sampleRate));
msg->setInt32("channel-count", numChannels);
msg->setInt32("sample-rate", sampleRate);
int32_t isADTS;
if (meta->findInt32(kKeyIsADTS, &isADTS) && isADTS != 0) {
msg->setInt32("is-adts", true);
}
}
uint32_t type;
const void *data;
size_t size;
if (meta->findData(kKeyAVCC, &type, &data, &size)) {
// Parse the AVCDecoderConfigurationRecord
const uint8_t *ptr = (const uint8_t *)data;
CHECK(size >= 7);
CHECK_EQ((unsigned)ptr[0], 1u); // configurationVersion == 1
uint8_t profile __unused = ptr[1];
uint8_t level __unused = ptr[3];
// There is decodable content out there that fails the following
// assertion, let's be lenient for now...
// CHECK((ptr[4] >> 2) == 0x3f); // reserved
size_t lengthSize __unused = 1 + (ptr[4] & 3);
// commented out check below as H264_QVGA_500_NO_AUDIO.3gp
// violates it...
// CHECK((ptr[5] >> 5) == 7); // reserved
size_t numSeqParameterSets = ptr[5] & 31;
ptr += 6;
size -= 6;
sp<ABuffer> buffer = new ABuffer(1024);
buffer->setRange(0, 0);
for (size_t i = 0; i < numSeqParameterSets; ++i) {
CHECK(size >= 2);
size_t length = U16_AT(ptr);
ptr += 2;
size -= 2;
CHECK(size >= length);
memcpy(buffer->data() + buffer->size(), "\x00\x00\x00\x01", 4);
memcpy(buffer->data() + buffer->size() + 4, ptr, length);
buffer->setRange(0, buffer->size() + 4 + length);
ptr += length;
size -= length;
}
buffer->meta()->setInt32("csd", true);
mCSD.push(buffer);
buffer = new ABuffer(1024);
buffer->setRange(0, 0);
CHECK(size >= 1);
size_t numPictureParameterSets = *ptr;
++ptr;
--size;
for (size_t i = 0; i < numPictureParameterSets; ++i) {
CHECK(size >= 2);
size_t length = U16_AT(ptr);
ptr += 2;
size -= 2;
CHECK(size >= length);
memcpy(buffer->data() + buffer->size(), "\x00\x00\x00\x01", 4);
memcpy(buffer->data() + buffer->size() + 4, ptr, length);
buffer->setRange(0, buffer->size() + 4 + length);
ptr += length;
size -= length;
}
buffer->meta()->setInt32("csd", true);
mCSD.push(buffer);
msg->setBuffer("csd", buffer);
} else if (meta->findData(kKeyESDS, &type, &data, &size)) {
ESDS esds((const char *)data, size);
CHECK_EQ(esds.InitCheck(), (status_t)OK);
const void *codec_specific_data;
size_t codec_specific_data_size;
esds.getCodecSpecificInfo(
&codec_specific_data, &codec_specific_data_size);
sp<ABuffer> buffer = new ABuffer(codec_specific_data_size);
memcpy(buffer->data(), codec_specific_data,
codec_specific_data_size);
buffer->meta()->setInt32("csd", true);
mCSD.push(buffer);
} else if (meta->findData(kKeyVorbisInfo, &type, &data, &size)) {
sp<ABuffer> buffer = new ABuffer(size);
memcpy(buffer->data(), data, size);
buffer->meta()->setInt32("csd", true);
mCSD.push(buffer);
CHECK(meta->findData(kKeyVorbisBooks, &type, &data, &size));
buffer = new ABuffer(size);
memcpy(buffer->data(), data, size);
buffer->meta()->setInt32("csd", true);
mCSD.push(buffer);
}
int32_t maxInputSize;
if (meta->findInt32(kKeyMaxInputSize, &maxInputSize)) {
msg->setInt32("max-input-size", maxInputSize);
}
return msg;
}
void onFillThisBuffer(const sp<AMessage> &msg) {
sp<AMessage> reply;
CHECK(msg->findMessage("reply", &reply));
if (mSource == NULL || mSeekState == SEEK_FLUSHING) {
reply->setInt32("err", ERROR_END_OF_STREAM);
reply->post();
return;
}
sp<ABuffer> outBuffer;
CHECK(msg->findBuffer("buffer", &outBuffer));
if (mCSDIndex < mCSD.size()) {
outBuffer = mCSD.editItemAt(mCSDIndex++);
outBuffer->meta()->setInt64("timeUs", 0);
} else {
size_t sizeLeft = outBuffer->capacity();
outBuffer->setRange(0, 0);
int32_t n = 0;
for (;;) {
MediaBuffer *inBuffer;
if (mLeftOverBuffer != NULL) {
inBuffer = mLeftOverBuffer;
mLeftOverBuffer = NULL;
} else if (mFinalResult != OK) {
break;
} else {
MediaSource::ReadOptions options;
if (mSeekState == SEEK_FLUSH_COMPLETED) {
options.setSeekTo(mSeekTimeUs);
mSeekState = SEEK_NONE;
}
status_t err = mSource->read(&inBuffer, &options);
if (err != OK) {
mFinalResult = err;
break;
}
}
size_t sizeNeeded = inBuffer->range_length();
if (mIsVorbis) {
// Vorbis data is suffixed with the number of
// valid samples on the page.
sizeNeeded += sizeof(int32_t);
}
if (sizeNeeded > sizeLeft) {
if (outBuffer->size() == 0) {
ALOGE("Unable to fit even a single input buffer of size %zu.",
sizeNeeded);
}
CHECK_GT(outBuffer->size(), 0u);
mLeftOverBuffer = inBuffer;
break;
}
++n;
if (outBuffer->size() == 0) {
int64_t timeUs;
CHECK(inBuffer->meta_data()->findInt64(kKeyTime, &timeUs));
outBuffer->meta()->setInt64("timeUs", timeUs);
}
memcpy(outBuffer->data() + outBuffer->size(),
(const uint8_t *)inBuffer->data()
+ inBuffer->range_offset(),
inBuffer->range_length());
if (mIsVorbis) {
int32_t numPageSamples;
if (!inBuffer->meta_data()->findInt32(
kKeyValidSamples, &numPageSamples)) {
numPageSamples = -1;
}
memcpy(outBuffer->data()
+ outBuffer->size() + inBuffer->range_length(),
&numPageSamples, sizeof(numPageSamples));
}
outBuffer->setRange(
0, outBuffer->size() + sizeNeeded);
sizeLeft -= sizeNeeded;
inBuffer->release();
inBuffer = NULL;
break; // Don't coalesce
}
ALOGV("coalesced %d input buffers", n);
if (outBuffer->size() == 0) {
CHECK_NE(mFinalResult, (status_t)OK);
reply->setInt32("err", mFinalResult);
reply->post();
return;
}
}
reply->setBuffer("buffer", outBuffer);
reply->post();
}
void onDrainThisBuffer(const sp<AMessage> &msg) {
sp<ABuffer> buffer;
CHECK(msg->findBuffer("buffer", &buffer));
mTotalBytesReceived += buffer->size();
sp<AMessage> reply;
CHECK(msg->findMessage("reply", &reply));
if (mRenderToSurface) {
reply->setInt32("render", 1);
}
reply->post();
}
DISALLOW_EVIL_CONSTRUCTORS(Controller);
};
static void usage(const char *me) {
fprintf(stderr, "usage: %s\n", me);
fprintf(stderr, " -h(elp)\n");
fprintf(stderr, " -a(udio)\n");
fprintf(stderr,
" -S(urface) Allocate output buffers on a surface.\n"
" -R(ender) Render surface-allocated buffers.\n");
}
int main(int argc, char **argv) {
android::ProcessState::self()->startThreadPool();
bool decodeAudio = false;
bool useSurface = false;
bool renderToSurface = false;
int res;
while ((res = getopt(argc, argv, "haSR")) >= 0) {
switch (res) {
case 'a':
decodeAudio = true;
break;
case 'S':
useSurface = true;
break;
case 'R':
renderToSurface = true;
break;
case '?':
case 'h':
default:
{
usage(argv[0]);
return 1;
}
}
}
argc -= optind;
argv += optind;
if (argc != 1) {
usage(argv[-optind]);
return 1;
}
DataSource::RegisterDefaultSniffers();
sp<ALooper> looper = new ALooper;
looper->setName("sf2");
sp<SurfaceComposerClient> composerClient;
sp<SurfaceControl> control;
sp<Surface> surface;
if (!decodeAudio && useSurface) {
composerClient = new SurfaceComposerClient;
CHECK_EQ(composerClient->initCheck(), (status_t)OK);
control = composerClient->createSurface(
String8("A Surface"),
1280,
800,
PIXEL_FORMAT_RGB_565,
0);
CHECK(control != NULL);
CHECK(control->isValid());
SurfaceComposerClient::openGlobalTransaction();
CHECK_EQ(control->setLayer(INT_MAX), (status_t)OK);
CHECK_EQ(control->show(), (status_t)OK);
SurfaceComposerClient::closeGlobalTransaction();
surface = control->getSurface();
CHECK(surface != NULL);
CHECK_EQ((status_t)OK,
native_window_api_connect(
surface.get(), NATIVE_WINDOW_API_MEDIA));
}
sp<Controller> controller =
new Controller(argv[0], decodeAudio, surface, renderToSurface);
looper->registerHandler(controller);
signal(SIGINT, mysighandler);
controller->startAsync();
CHECK_EQ(looper->start(true /* runOnCallingThread */), (status_t)OK);
looper->unregisterHandler(controller->id());
if (!decodeAudio && useSurface) {
CHECK_EQ((status_t)OK,
native_window_api_disconnect(
surface.get(), NATIVE_WINDOW_API_MEDIA));
composerClient->dispose();
}
return 0;
}