You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
402 lines
11 KiB
402 lines
11 KiB
/*
|
|
* 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 "stream"
|
|
#include "utils/Log.h"
|
|
|
|
#include <binder/ProcessState.h>
|
|
#include <cutils/properties.h> // for property_get
|
|
|
|
#include <datasource/DataSourceFactory.h>
|
|
#include <media/DataSource.h>
|
|
#include <media/IMediaHTTPService.h>
|
|
#include <media/IStreamSource.h>
|
|
#include <media/mediaplayer.h>
|
|
#include <media/stagefright/MediaSource.h>
|
|
#include <media/stagefright/foundation/ADebug.h>
|
|
#include <media/stagefright/foundation/AMessage.h>
|
|
#include <media/stagefright/InterfaceUtils.h>
|
|
#include <media/stagefright/MPEG2TSWriter.h>
|
|
#include <media/stagefright/MediaExtractor.h>
|
|
#include <media/stagefright/MediaExtractorFactory.h>
|
|
#include <media/stagefright/MetaData.h>
|
|
|
|
#include <binder/IServiceManager.h>
|
|
#include <media/IMediaPlayerService.h>
|
|
#include <gui/ISurfaceComposer.h>
|
|
#include <gui/SurfaceComposerClient.h>
|
|
#include <gui/Surface.h>
|
|
|
|
#include <fcntl.h>
|
|
#include <ui/DisplayConfig.h>
|
|
|
|
using namespace android;
|
|
|
|
struct MyStreamSource : public BnStreamSource {
|
|
// Object assumes ownership of fd.
|
|
explicit MyStreamSource(int fd);
|
|
|
|
virtual void setListener(const sp<IStreamListener> &listener);
|
|
virtual void setBuffers(const Vector<sp<IMemory> > &buffers);
|
|
|
|
virtual void onBufferAvailable(size_t index);
|
|
|
|
protected:
|
|
virtual ~MyStreamSource();
|
|
|
|
private:
|
|
int mFd;
|
|
off64_t mFileSize;
|
|
uint64_t mNumPacketsSent;
|
|
|
|
sp<IStreamListener> mListener;
|
|
Vector<sp<IMemory> > mBuffers;
|
|
|
|
DISALLOW_EVIL_CONSTRUCTORS(MyStreamSource);
|
|
};
|
|
|
|
MyStreamSource::MyStreamSource(int fd)
|
|
: mFd(fd),
|
|
mFileSize(0),
|
|
mNumPacketsSent(0) {
|
|
CHECK_GE(fd, 0);
|
|
|
|
mFileSize = lseek64(fd, 0, SEEK_END);
|
|
lseek64(fd, 0, SEEK_SET);
|
|
}
|
|
|
|
MyStreamSource::~MyStreamSource() {
|
|
close(mFd);
|
|
mFd = -1;
|
|
}
|
|
|
|
void MyStreamSource::setListener(const sp<IStreamListener> &listener) {
|
|
mListener = listener;
|
|
}
|
|
|
|
void MyStreamSource::setBuffers(const Vector<sp<IMemory> > &buffers) {
|
|
mBuffers = buffers;
|
|
}
|
|
|
|
void MyStreamSource::onBufferAvailable(size_t index) {
|
|
CHECK_LT(index, mBuffers.size());
|
|
|
|
#if 0
|
|
if (mNumPacketsSent >= 20000) {
|
|
ALOGI("signalling discontinuity now");
|
|
|
|
off64_t offset = 0;
|
|
CHECK((offset % 188) == 0);
|
|
|
|
lseek(mFd, offset, SEEK_SET);
|
|
|
|
sp<AMessage> extra = new AMessage;
|
|
extra->setInt32(IStreamListener::kKeyFormatChange, 0);
|
|
|
|
mListener->issueCommand(
|
|
IStreamListener::DISCONTINUITY, false /* synchronous */, extra);
|
|
|
|
mNumPacketsSent = 0;
|
|
}
|
|
#endif
|
|
|
|
sp<IMemory> mem = mBuffers.itemAt(index);
|
|
|
|
ssize_t n = read(mFd, mem->unsecurePointer(), mem->size());
|
|
if (n <= 0) {
|
|
mListener->issueCommand(IStreamListener::EOS, false /* synchronous */);
|
|
} else {
|
|
mListener->queueBuffer(index, n);
|
|
|
|
mNumPacketsSent += n / 188;
|
|
}
|
|
}
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
struct MyConvertingStreamSource : public BnStreamSource {
|
|
explicit MyConvertingStreamSource(const char *filename);
|
|
|
|
virtual void setListener(const sp<IStreamListener> &listener);
|
|
virtual void setBuffers(const Vector<sp<IMemory> > &buffers);
|
|
|
|
virtual void onBufferAvailable(size_t index);
|
|
|
|
protected:
|
|
virtual ~MyConvertingStreamSource();
|
|
|
|
private:
|
|
Mutex mLock;
|
|
Condition mCondition;
|
|
|
|
sp<IStreamListener> mListener;
|
|
Vector<sp<IMemory> > mBuffers;
|
|
|
|
sp<MPEG2TSWriter> mWriter;
|
|
|
|
ssize_t mCurrentBufferIndex;
|
|
size_t mCurrentBufferOffset;
|
|
|
|
List<size_t> mBufferQueue;
|
|
|
|
static ssize_t WriteDataWrapper(void *me, const void *data, size_t size);
|
|
ssize_t writeData(const void *data, size_t size);
|
|
|
|
DISALLOW_EVIL_CONSTRUCTORS(MyConvertingStreamSource);
|
|
};
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
MyConvertingStreamSource::MyConvertingStreamSource(const char *filename)
|
|
: mCurrentBufferIndex(-1),
|
|
mCurrentBufferOffset(0) {
|
|
sp<DataSource> dataSource =
|
|
DataSourceFactory::getInstance()->CreateFromURI(NULL /* httpService */, filename);
|
|
|
|
CHECK(dataSource != NULL);
|
|
|
|
sp<IMediaExtractor> extractor = MediaExtractorFactory::Create(dataSource);
|
|
CHECK(extractor != NULL);
|
|
|
|
mWriter = new MPEG2TSWriter(
|
|
this, &MyConvertingStreamSource::WriteDataWrapper);
|
|
|
|
size_t numTracks = extractor->countTracks();
|
|
for (size_t i = 0; i < numTracks; ++i) {
|
|
const sp<MetaData> &meta = extractor->getTrackMetaData(i);
|
|
|
|
const char *mime;
|
|
CHECK(meta->findCString(kKeyMIMEType, &mime));
|
|
|
|
if (strncasecmp("video/", mime, 6) && strncasecmp("audio/", mime, 6)) {
|
|
continue;
|
|
}
|
|
|
|
sp<MediaSource> track = CreateMediaSourceFromIMediaSource(extractor->getTrack(i));
|
|
if (track == nullptr) {
|
|
fprintf(stderr, "skip NULL track %zu, total tracks %zu\n", i, numTracks);
|
|
continue;
|
|
}
|
|
CHECK_EQ(mWriter->addSource(track), (status_t)OK);
|
|
}
|
|
|
|
CHECK_EQ(mWriter->start(), (status_t)OK);
|
|
}
|
|
|
|
MyConvertingStreamSource::~MyConvertingStreamSource() {
|
|
}
|
|
|
|
void MyConvertingStreamSource::setListener(
|
|
const sp<IStreamListener> &listener) {
|
|
mListener = listener;
|
|
}
|
|
|
|
void MyConvertingStreamSource::setBuffers(
|
|
const Vector<sp<IMemory> > &buffers) {
|
|
mBuffers = buffers;
|
|
}
|
|
|
|
ssize_t MyConvertingStreamSource::WriteDataWrapper(
|
|
void *me, const void *data, size_t size) {
|
|
return static_cast<MyConvertingStreamSource *>(me)->writeData(data, size);
|
|
}
|
|
|
|
ssize_t MyConvertingStreamSource::writeData(const void *data, size_t size) {
|
|
size_t totalWritten = 0;
|
|
|
|
while (size > 0) {
|
|
Mutex::Autolock autoLock(mLock);
|
|
|
|
if (mCurrentBufferIndex < 0) {
|
|
while (mBufferQueue.empty()) {
|
|
mCondition.wait(mLock);
|
|
}
|
|
|
|
mCurrentBufferIndex = *mBufferQueue.begin();
|
|
mCurrentBufferOffset = 0;
|
|
|
|
mBufferQueue.erase(mBufferQueue.begin());
|
|
}
|
|
|
|
sp<IMemory> mem = mBuffers.itemAt(mCurrentBufferIndex);
|
|
|
|
size_t copy = size;
|
|
if (copy + mCurrentBufferOffset > mem->size()) {
|
|
copy = mem->size() - mCurrentBufferOffset;
|
|
}
|
|
|
|
memcpy((uint8_t *)mem->unsecurePointer() + mCurrentBufferOffset, data, copy);
|
|
mCurrentBufferOffset += copy;
|
|
|
|
if (mCurrentBufferOffset == mem->size()) {
|
|
mListener->queueBuffer(mCurrentBufferIndex, mCurrentBufferOffset);
|
|
mCurrentBufferIndex = -1;
|
|
}
|
|
|
|
data = (const uint8_t *)data + copy;
|
|
size -= copy;
|
|
|
|
totalWritten += copy;
|
|
}
|
|
|
|
return (ssize_t)totalWritten;
|
|
}
|
|
|
|
void MyConvertingStreamSource::onBufferAvailable(size_t index) {
|
|
Mutex::Autolock autoLock(mLock);
|
|
|
|
mBufferQueue.push_back(index);
|
|
mCondition.signal();
|
|
|
|
if (mWriter->reachedEOS()) {
|
|
if (mCurrentBufferIndex >= 0) {
|
|
mListener->queueBuffer(mCurrentBufferIndex, mCurrentBufferOffset);
|
|
mCurrentBufferIndex = -1;
|
|
}
|
|
|
|
mListener->issueCommand(IStreamListener::EOS, false /* synchronous */);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
struct MyClient : public BnMediaPlayerClient {
|
|
MyClient()
|
|
: mEOS(false) {
|
|
}
|
|
|
|
virtual void notify(int msg, int ext1 __unused, int ext2 __unused, const Parcel *obj __unused) {
|
|
Mutex::Autolock autoLock(mLock);
|
|
|
|
if (msg == MEDIA_ERROR || msg == MEDIA_PLAYBACK_COMPLETE) {
|
|
mEOS = true;
|
|
mCondition.signal();
|
|
}
|
|
}
|
|
|
|
void waitForEOS() {
|
|
Mutex::Autolock autoLock(mLock);
|
|
while (!mEOS) {
|
|
mCondition.wait(mLock);
|
|
}
|
|
}
|
|
|
|
protected:
|
|
virtual ~MyClient() {
|
|
}
|
|
|
|
private:
|
|
Mutex mLock;
|
|
Condition mCondition;
|
|
|
|
bool mEOS;
|
|
|
|
DISALLOW_EVIL_CONSTRUCTORS(MyClient);
|
|
};
|
|
|
|
int main(int argc, char **argv) {
|
|
android::ProcessState::self()->startThreadPool();
|
|
|
|
if (argc != 2) {
|
|
fprintf(stderr, "Usage: %s filename\n", argv[0]);
|
|
return 1;
|
|
}
|
|
|
|
sp<SurfaceComposerClient> composerClient = new SurfaceComposerClient;
|
|
CHECK_EQ(composerClient->initCheck(), (status_t)OK);
|
|
|
|
const sp<IBinder> display = SurfaceComposerClient::getInternalDisplayToken();
|
|
CHECK(display != nullptr);
|
|
|
|
DisplayConfig config;
|
|
CHECK_EQ(SurfaceComposerClient::getActiveDisplayConfig(display, &config), NO_ERROR);
|
|
|
|
const ui::Size& resolution = config.resolution;
|
|
const ssize_t displayWidth = resolution.getWidth();
|
|
const ssize_t displayHeight = resolution.getHeight();
|
|
|
|
ALOGV("display is %zd x %zd\n", displayWidth, displayHeight);
|
|
|
|
sp<SurfaceControl> control =
|
|
composerClient->createSurface(
|
|
String8("A Surface"),
|
|
displayWidth,
|
|
displayHeight,
|
|
PIXEL_FORMAT_RGB_565,
|
|
0);
|
|
|
|
CHECK(control != NULL);
|
|
CHECK(control->isValid());
|
|
|
|
SurfaceComposerClient::Transaction{}
|
|
.setLayer(control, INT_MAX)
|
|
.show(control)
|
|
.apply();
|
|
|
|
sp<Surface> surface = control->getSurface();
|
|
CHECK(surface != NULL);
|
|
|
|
sp<IServiceManager> sm = defaultServiceManager();
|
|
sp<IBinder> binder = sm->getService(String16("media.player"));
|
|
sp<IMediaPlayerService> service = interface_cast<IMediaPlayerService>(binder);
|
|
|
|
CHECK(service.get() != NULL);
|
|
|
|
sp<MyClient> client = new MyClient;
|
|
|
|
sp<IStreamSource> source;
|
|
|
|
bool usemp4 = property_get_bool("media.stagefright.use-mp4source", false);
|
|
|
|
size_t len = strlen(argv[1]);
|
|
if ((!usemp4 && len >= 3 && !strcasecmp(".ts", &argv[1][len - 3])) ||
|
|
(usemp4 && len >= 4 &&
|
|
(!strcasecmp(".mp4", &argv[1][len - 4])
|
|
|| !strcasecmp(".3gp", &argv[1][len- 4])
|
|
|| !strcasecmp(".3g2", &argv[1][len- 4])))) {
|
|
int fd = open(argv[1], O_RDONLY);
|
|
|
|
if (fd < 0) {
|
|
fprintf(stderr, "Failed to open file '%s'.", argv[1]);
|
|
return 1;
|
|
}
|
|
|
|
source = new MyStreamSource(fd);
|
|
} else {
|
|
printf("Converting file to transport stream for streaming...\n");
|
|
|
|
source = new MyConvertingStreamSource(argv[1]);
|
|
}
|
|
|
|
sp<IMediaPlayer> player =
|
|
service->create(client, AUDIO_SESSION_ALLOCATE);
|
|
|
|
if (player != NULL && player->setDataSource(source) == NO_ERROR) {
|
|
player->setVideoSurfaceTexture(surface->getIGraphicBufferProducer());
|
|
player->start();
|
|
|
|
client->waitForEOS();
|
|
|
|
player->stop();
|
|
} else {
|
|
fprintf(stderr, "failed to instantiate player.\n");
|
|
}
|
|
|
|
composerClient->dispose();
|
|
|
|
return 0;
|
|
}
|