diff options
81 files changed, 13327 insertions, 14 deletions
diff --git a/cmds/stagefright/Android.mk b/cmds/stagefright/Android.mk new file mode 100644 index 0000000..fd681a2 --- /dev/null +++ b/cmds/stagefright/Android.mk @@ -0,0 +1,66 @@ +ifeq ($(BUILD_WITH_STAGEFRIGHT),true) + +LOCAL_PATH:= $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + stagefright.cpp + +LOCAL_SHARED_LIBRARIES := \ + libstagefright + +LOCAL_C_INCLUDES:= \ + frameworks/base/media/libstagefright \ + $(TOP)/external/opencore/extern_libs_v2/khronos/openmax/include \ + $(TOP)/external/opencore/android + +LOCAL_CFLAGS += -Wno-multichar + +LOCAL_MODULE:= stagefright + +include $(BUILD_EXECUTABLE) + +################################################################################ + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + record.cpp + +LOCAL_SHARED_LIBRARIES := \ + libstagefright + +LOCAL_C_INCLUDES:= \ + frameworks/base/media/libstagefright \ + $(TOP)/external/opencore/extern_libs_v2/khronos/openmax/include \ + $(TOP)/external/opencore/android + +LOCAL_CFLAGS += -Wno-multichar + +LOCAL_MODULE:= record + +include $(BUILD_EXECUTABLE) + +################################################################################ + +# include $(CLEAR_VARS) +# +# LOCAL_SRC_FILES:= \ +# play.cpp +# +# LOCAL_SHARED_LIBRARIES := \ +# libstagefright +# +# LOCAL_C_INCLUDES:= \ +# frameworks/base/media/libstagefright \ +# $(TOP)/external/opencore/extern_libs_v2/khronos/openmax/include \ +# $(TOP)/external/opencore/android +# +# LOCAL_CFLAGS += -Wno-multichar +# +# LOCAL_MODULE:= play +# +# include $(BUILD_EXECUTABLE) + +endif diff --git a/cmds/stagefright/WaveWriter.h b/cmds/stagefright/WaveWriter.h new file mode 100644 index 0000000..a0eb66e --- /dev/null +++ b/cmds/stagefright/WaveWriter.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2009 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. + */ + +#ifndef ANDROID_WAVEWRITER_H_ + +#define ANDROID_WAVEWRITER_H_ + +namespace android { + +class WaveWriter { +public: + WaveWriter(const char *filename, + uint16_t num_channels, uint32_t sampling_rate) + : mFile(fopen(filename, "wb")), + mTotalBytes(0) { + fwrite("RIFFxxxxWAVEfmt \x10\x00\x00\x00\x01\x00", 1, 22, mFile); + write_u16(num_channels); + write_u32(sampling_rate); + write_u32(sampling_rate * num_channels * 2); + write_u16(num_channels * 2); + write_u16(16); + fwrite("dataxxxx", 1, 8, mFile); + } + + ~WaveWriter() { + fseek(mFile, 40, SEEK_SET); + write_u32(mTotalBytes); + + fseek(mFile, 4, SEEK_SET); + write_u32(36 + mTotalBytes); + + fclose(mFile); + mFile = NULL; + } + + void Append(const void *data, size_t size) { + fwrite(data, 1, size, mFile); + mTotalBytes += size; + } + +private: + void write_u16(uint16_t x) { + fputc(x & 0xff, mFile); + fputc(x >> 8, mFile); + } + + void write_u32(uint32_t x) { + write_u16(x & 0xffff); + write_u16(x >> 16); + } + + FILE *mFile; + size_t mTotalBytes; +}; + +} // namespace android + +#endif // ANDROID_WAVEWRITER_H_ diff --git a/cmds/stagefright/play.cpp b/cmds/stagefright/play.cpp new file mode 100644 index 0000000..c6e778e --- /dev/null +++ b/cmds/stagefright/play.cpp @@ -0,0 +1,295 @@ +/* + * Copyright (C) 2009 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. + */ + +#include <binder/ProcessState.h> +#include <media/stagefright/OMXClient.h> +#include <media/stagefright/TimedEventQueue.h> +#include <media/stagefright/MPEG4Extractor.h> +#include <media/stagefright/MediaSource.h> +#include <media/stagefright/MetaData.h> +#include <media/stagefright/MmapSource.h> +#include <media/stagefright/OMXDecoder.h> + +using namespace android; + +struct NewPlayer { + NewPlayer(); + ~NewPlayer(); + + void setSource(const char *uri); + void start(); + void pause(); + void stop(); + +private: + struct PlayerEvent : public TimedEventQueue::Event { + PlayerEvent(NewPlayer *player, + void (NewPlayer::*method)(int64_t realtime_us)) + : mPlayer(player), + mMethod(method) { + } + + virtual void fire(TimedEventQueue *queue, int64_t realtime_us) { + (mPlayer->*mMethod)(realtime_us); + } + + private: + NewPlayer *mPlayer; + void (NewPlayer::*mMethod)(int64_t realtime_us); + + PlayerEvent(const PlayerEvent &); + PlayerEvent &operator=(const PlayerEvent &); + }; + + struct PlayVideoFrameEvent : public TimedEventQueue::Event { + PlayVideoFrameEvent(NewPlayer *player, MediaBuffer *buffer) + : mPlayer(player), + mBuffer(buffer) { + } + + virtual ~PlayVideoFrameEvent() { + if (mBuffer != NULL) { + mBuffer->release(); + mBuffer = NULL; + } + } + + virtual void fire(TimedEventQueue *queue, int64_t realtime_us) { + mPlayer->onPlayVideoFrame(realtime_us, mBuffer); + mBuffer = NULL; + } + + private: + NewPlayer *mPlayer; + MediaBuffer *mBuffer; + + PlayVideoFrameEvent(const PlayVideoFrameEvent &); + PlayVideoFrameEvent &operator=(const PlayVideoFrameEvent &); + }; + + OMXClient mClient; + + MPEG4Extractor *mExtractor; + MediaSource *mAudioSource; + OMXDecoder *mAudioDecoder; + MediaSource *mVideoSource; + OMXDecoder *mVideoDecoder; + + int32_t mVideoWidth, mVideoHeight; + + TimedEventQueue mQueue; + wp<TimedEventQueue::Event> mPlayVideoFrameEvent; + + int64_t mMediaTimeUsStart; + int64_t mRealTimeUsStart; + + void setAudioSource(MediaSource *source); + void setVideoSource(MediaSource *source); + + int64_t approxRealTime(int64_t mediatime_us) const; + + void onStart(int64_t realtime_us); + void onPause(int64_t realtime_us); + void onFetchVideoFrame(int64_t realtime_us); + void onPlayVideoFrame(int64_t realtime_us, MediaBuffer *buffer); + + static int64_t getMediaBufferTimeUs(MediaBuffer *buffer); + + NewPlayer(const NewPlayer &); + NewPlayer &operator=(const NewPlayer &); +}; + +NewPlayer::NewPlayer() + : mExtractor(NULL), + mAudioSource(NULL), + mAudioDecoder(NULL), + mVideoSource(NULL), + mVideoDecoder(NULL), + mVideoWidth(0), + mVideoHeight(0) { + status_t err = mClient.connect(); + assert(err == OK); +} + +NewPlayer::~NewPlayer() { + stop(); + + mClient.disconnect(); +} + +void NewPlayer::setSource(const char *uri) { + stop(); + + mExtractor = new MPEG4Extractor(new MmapSource(uri)); + + int num_tracks; + status_t err = mExtractor->countTracks(&num_tracks); + assert(err == OK); + + for (int i = 0; i < num_tracks; ++i) { + const sp<MetaData> meta = mExtractor->getTrackMetaData(i); + assert(meta != NULL); + + const char *mime; + if (!meta->findCString(kKeyMIMEType, &mime)) { + continue; + } + + bool is_audio = false; + bool is_acceptable = false; + if (!strncasecmp(mime, "audio/", 6)) { + is_audio = true; + is_acceptable = (mAudioSource == NULL); + } else if (!strncasecmp(mime, "video/", 6)) { + is_acceptable = (mVideoSource == NULL); + } + + if (!is_acceptable) { + continue; + } + + MediaSource *source; + if (mExtractor->getTrack(i, &source) != OK) { + continue; + } + + if (is_audio) { + setAudioSource(source); + } else { + setVideoSource(source); + } + } +} + +void NewPlayer::setAudioSource(MediaSource *source) { + mAudioSource = source; + + sp<MetaData> meta = source->getFormat(); + + mAudioDecoder = OMXDecoder::Create(&mClient, meta); + mAudioDecoder->setSource(source); +} + +void NewPlayer::setVideoSource(MediaSource *source) { + mVideoSource = source; + + sp<MetaData> meta = source->getFormat(); + + bool success = meta->findInt32(kKeyWidth, &mVideoWidth); + assert(success); + + success = meta->findInt32(kKeyHeight, &mVideoHeight); + assert(success); + + mVideoDecoder = OMXDecoder::Create(&mClient, meta); + mVideoDecoder->setSource(source); +} + +void NewPlayer::start() { + mQueue.start(); + mQueue.postEvent(new PlayerEvent(this, &NewPlayer::onStart)); +} + +void NewPlayer::pause() { + mQueue.postEvent(new PlayerEvent(this, &NewPlayer::onPause)); +} + +void NewPlayer::stop() { + mQueue.stop(); + + delete mVideoDecoder; + mVideoDecoder = NULL; + delete mVideoSource; + mVideoSource = NULL; + mVideoWidth = mVideoHeight = 0; + + delete mAudioDecoder; + mAudioDecoder = NULL; + delete mAudioSource; + mAudioSource = NULL; + + delete mExtractor; + mExtractor = NULL; +} + +int64_t NewPlayer::approxRealTime(int64_t mediatime_us) const { + return mRealTimeUsStart + (mediatime_us - mMediaTimeUsStart); +} + +void NewPlayer::onStart(int64_t realtime_us) { + mRealTimeUsStart = TimedEventQueue::getRealTimeUs(); + + if (mVideoDecoder != NULL) { + mQueue.postEvent(new PlayerEvent(this, &NewPlayer::onFetchVideoFrame)); + } +} + +void NewPlayer::onFetchVideoFrame(int64_t realtime_us) { + MediaBuffer *buffer; + status_t err = mVideoDecoder->read(&buffer); + assert(err == OK); + + int64_t mediatime_us = getMediaBufferTimeUs(buffer); + + sp<TimedEventQueue::Event> event = new PlayVideoFrameEvent(this, buffer); + mPlayVideoFrameEvent = event; + + mQueue.postTimedEvent(event, approxRealTime(mediatime_us)); +} + +// static +int64_t NewPlayer::getMediaBufferTimeUs(MediaBuffer *buffer) { + int32_t units, scale; + bool success = + buffer->meta_data()->findInt32(kKeyTimeUnits, &units); + assert(success); + success = + buffer->meta_data()->findInt32(kKeyTimeScale, &scale); + assert(success); + + return (int64_t)units * 1000000 / scale; +} + +void NewPlayer::onPlayVideoFrame(int64_t realtime_us, MediaBuffer *buffer) { + LOGI("playing video frame (mediatime: %.2f sec)\n", + getMediaBufferTimeUs(buffer) / 1E6); + fflush(stdout); + + buffer->release(); + buffer = NULL; + + mQueue.postEvent(new PlayerEvent(this, &NewPlayer::onFetchVideoFrame)); +} + +void NewPlayer::onPause(int64_t realtime_us) { +} + +int main(int argc, char **argv) { + android::ProcessState::self()->startThreadPool(); + + if (argc != 2) { + fprintf(stderr, "usage: %s filename\n", argv[0]); + return 1; + } + + NewPlayer player; + player.setSource(argv[1]); + player.start(); + sleep(10); + player.stop(); + + return 0; +} diff --git a/cmds/stagefright/record.cpp b/cmds/stagefright/record.cpp new file mode 100644 index 0000000..12bdead --- /dev/null +++ b/cmds/stagefright/record.cpp @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2009 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. + */ + +#undef NDEBUG +#include <assert.h> + +#include <binder/ProcessState.h> +#include <media/stagefright/CameraSource.h> +#include <media/stagefright/MediaBufferGroup.h> +#include <media/stagefright/MetaData.h> +#include <media/stagefright/MPEG4Extractor.h> +#include <media/stagefright/MPEG4Writer.h> +#include <media/stagefright/MmapSource.h> +#include <media/stagefright/OMXClient.h> +#include <media/stagefright/OMXDecoder.h> + +using namespace android; + +class DummySource : public MediaSource { +public: + DummySource(int width, int height) + : mSize((width * height * 3) / 2) { + mGroup.add_buffer(new MediaBuffer(mSize)); + } + + virtual ::status_t getMaxSampleSize(size_t *max_size) { + *max_size = mSize; + return ::OK; + } + + virtual ::status_t read(MediaBuffer **buffer) { + ::status_t err = mGroup.acquire_buffer(buffer); + if (err != ::OK) { + return err; + } + + char x = (char)((double)rand() / RAND_MAX * 255); + memset((*buffer)->data(), x, mSize); + (*buffer)->set_range(0, mSize); + + return ::OK; + } + +private: + MediaBufferGroup mGroup; + size_t mSize; + + DummySource(const DummySource &); + DummySource &operator=(const DummySource &); +}; + +int main(int argc, char **argv) { + android::ProcessState::self()->startThreadPool(); + +#if 1 + if (argc != 2) { + fprintf(stderr, "usage: %s filename\n", argv[0]); + return 1; + } + + MPEG4Extractor extractor(new MmapSource(argv[1])); + int num_tracks; + assert(extractor.countTracks(&num_tracks) == ::OK); + + MediaSource *source = NULL; + sp<MetaData> meta; + for (int i = 0; i < num_tracks; ++i) { + meta = extractor.getTrackMetaData(i); + assert(meta.get() != NULL); + + const char *mime; + if (!meta->findCString(kKeyMIMEType, &mime)) { + continue; + } + + if (strncasecmp(mime, "video/", 6)) { + continue; + } + + if (extractor.getTrack(i, &source) != ::OK) { + source = NULL; + continue; + } + break; + } + + if (source == NULL) { + fprintf(stderr, "Unable to find a suitable video track.\n"); + return 1; + } + + OMXClient client; + assert(client.connect() == android::OK); + + OMXDecoder *decoder = OMXDecoder::Create(&client, meta); + decoder->setSource(source); + + int width, height; + bool success = meta->findInt32(kKeyWidth, &width); + success = success && meta->findInt32(kKeyHeight, &height); + assert(success); + + sp<MetaData> enc_meta = new MetaData; + // enc_meta->setCString(kKeyMIMEType, "video/3gpp"); + enc_meta->setCString(kKeyMIMEType, "video/mp4v-es"); + enc_meta->setInt32(kKeyWidth, width); + enc_meta->setInt32(kKeyHeight, height); + + OMXDecoder *encoder = OMXDecoder::CreateEncoder(&client, enc_meta); + + encoder->setSource(decoder); + // encoder->setSource(meta, new DummySource(width, height)); + +#if 1 + MPEG4Writer writer("/sdcard/output.mp4"); + writer.addSource(enc_meta, encoder); + writer.start(); + sleep(120); + writer.stop(); +#else + encoder->start(); + + MediaBuffer *buffer; + while (encoder->read(&buffer) == ::OK) { + printf("got an output frame of size %d\n", buffer->range_length()); + + buffer->release(); + buffer = NULL; + } + + encoder->stop(); +#endif + + delete encoder; + encoder = NULL; + + delete decoder; + decoder = NULL; + + client.disconnect(); + + delete source; + source = NULL; +#endif + +#if 0 + CameraSource *source = CameraSource::Create(); + printf("source = %p\n", source); + + for (int i = 0; i < 100; ++i) { + MediaBuffer *buffer; + status_t err = source->read(&buffer); + assert(err == OK); + + printf("got a frame, data=%p, size=%d\n", + buffer->data(), buffer->range_length()); + + buffer->release(); + buffer = NULL; + } + + delete source; + source = NULL; +#endif + + return 0; +} + diff --git a/cmds/stagefright/stagefright.cpp b/cmds/stagefright/stagefright.cpp new file mode 100644 index 0000000..961942a --- /dev/null +++ b/cmds/stagefright/stagefright.cpp @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2009 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. + */ + +#include <sys/time.h> + +#undef NDEBUG +#include <assert.h> + +#include <pthread.h> +#include <stdlib.h> + +#include <binder/IServiceManager.h> +#include <binder/ProcessState.h> +#include <media/IMediaPlayerService.h> +#include <media/stagefright/AudioPlayer.h> +#include <media/stagefright/CachingDataSource.h> +#include <media/stagefright/ESDS.h> +#include <media/stagefright/FileSource.h> +#include <media/stagefright/MediaBuffer.h> +#include <media/stagefright/MediaPlayerImpl.h> +#include <media/stagefright/MediaExtractor.h> +#include <media/stagefright/MediaSource.h> +#include <media/stagefright/MetaData.h> +#include <media/stagefright/MmapSource.h> +#include <media/stagefright/OMXClient.h> +#include <media/stagefright/OMXDecoder.h> + +#include "WaveWriter.h" + +using namespace android; + +//////////////////////////////////////////////////////////////////////////////// + +static bool convertToWav( + OMXClient *client, const sp<MetaData> &meta, MediaSource *source) { + printf("convertToWav\n"); + + OMXDecoder *decoder = OMXDecoder::Create(client, meta); + + int32_t sampleRate; + bool success = meta->findInt32(kKeySampleRate, &sampleRate); + assert(success); + + int32_t numChannels; + success = meta->findInt32(kKeyChannelCount, &numChannels); + assert(success); + + const char *mime; + success = meta->findCString(kKeyMIMEType, &mime); + assert(success); + + if (!strcasecmp("audio/3gpp", mime)) { + numChannels = 1; // XXX + } + + WaveWriter writer("/sdcard/Music/shoutcast.wav", numChannels, sampleRate); + + decoder->setSource(source); + for (int i = 0; i < 100; ++i) { + MediaBuffer *buffer; + + ::status_t err = decoder->read(&buffer); + if (err != ::OK) { + break; + } + + writer.Append((const char *)buffer->data() + buffer->range_offset(), + buffer->range_length()); + + buffer->release(); + buffer = NULL; + } + + delete decoder; + decoder = NULL; + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// + +static int64_t getNowUs() { + struct timeval tv; + gettimeofday(&tv, NULL); + + return (int64_t)tv.tv_usec + tv.tv_sec * 1000000; +} + +int main(int argc, char **argv) { + android::ProcessState::self()->startThreadPool(); + + if (argc > 1 && !strcmp(argv[1], "--list")) { + sp<IServiceManager> sm = defaultServiceManager(); + sp<IBinder> binder = sm->getService(String16("media.player")); + sp<IMediaPlayerService> service = interface_cast<IMediaPlayerService>(binder); + + assert(service.get() != NULL); + + sp<IOMX> omx = service->createOMX(); + assert(omx.get() != NULL); + + List<String8> list; + omx->list_nodes(&list); + + for (List<String8>::iterator it = list.begin(); + it != list.end(); ++it) { + printf("%s\n", (*it).string()); + } + + return 0; + } + +#if 0 + MediaPlayerImpl player(argv[1]); + player.play(); + + sleep(10000); +#else + DataSource::RegisterDefaultSniffers(); + + OMXClient client; + status_t err = client.connect(); + + MmapSource *dataSource = new MmapSource(argv[1]); + MediaExtractor *extractor = MediaExtractor::Create(dataSource); + dataSource = NULL; + + int numTracks; + err = extractor->countTracks(&numTracks); + + sp<MetaData> meta; + int i; + for (i = 0; i < numTracks; ++i) { + meta = extractor->getTrackMetaData(i); + + const char *mime; + meta->findCString(kKeyMIMEType, &mime); + + if (!strncasecmp(mime, "video/", 6)) { + break; + } + } + + OMXDecoder *decoder = OMXDecoder::Create(&client, meta); + + if (decoder != NULL) { + MediaSource *source; + err = extractor->getTrack(i, &source); + + decoder->setSource(source); + + decoder->start(); + + int64_t startTime = getNowUs(); + + int n = 0; + MediaBuffer *buffer; + while ((err = decoder->read(&buffer)) == OK) { + if ((++n % 16) == 0) { + printf("."); + fflush(stdout); + } + + buffer->release(); + buffer = NULL; + } + decoder->stop(); + printf("\n"); + + int64_t delay = getNowUs() - startTime; + printf("avg. %.2f fps\n", n * 1E6 / delay); + + delete decoder; + decoder = NULL; + + delete source; + source = NULL; + } + + delete extractor; + extractor = NULL; + + client.disconnect(); +#endif + + return 0; +} diff --git a/include/media/IMediaPlayerService.h b/include/media/IMediaPlayerService.h index f6faf14..39b5e57 100644 --- a/include/media/IMediaPlayerService.h +++ b/include/media/IMediaPlayerService.h @@ -29,6 +29,7 @@ namespace android { class IMediaRecorder; +class IOMX; class IMediaPlayerService: public IInterface { @@ -41,6 +42,7 @@ public: virtual sp<IMediaPlayer> create(pid_t pid, const sp<IMediaPlayerClient>& client, int fd, int64_t offset, int64_t length) = 0; virtual sp<IMemory> decode(const char* url, uint32_t *pSampleRate, int* pNumChannels, int* pFormat) = 0; virtual sp<IMemory> decode(int fd, int64_t offset, int64_t length, uint32_t *pSampleRate, int* pNumChannels, int* pFormat) = 0; + virtual sp<IOMX> createOMX() = 0; }; // ---------------------------------------------------------------------------- diff --git a/include/media/IOMX.h b/include/media/IOMX.h new file mode 100644 index 0000000..5c61c50 --- /dev/null +++ b/include/media/IOMX.h @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2009 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. + */ + +#ifndef ANDROID_IOMX_H_ + +#define ANDROID_IOMX_H_ + +#include <binder/IInterface.h> +#include <utils/List.h> +#include <utils/String8.h> + +#include <OMX_Core.h> + +#define IOMX_USES_SOCKETS 0 + +namespace android { + +class IMemory; +class IOMXObserver; + +class IOMX : public IInterface { +public: + DECLARE_META_INTERFACE(OMX); + + typedef void *buffer_id; + typedef void *node_id; + +#if IOMX_USES_SOCKETS + // If successful, returns a socket descriptor used for further + // communication. Caller assumes ownership of "*sd". + virtual status_t connect(int *sd) = 0; +#endif + + virtual status_t list_nodes(List<String8> *list) = 0; + + virtual status_t allocate_node(const char *name, node_id *node) = 0; + virtual status_t free_node(node_id node) = 0; + + virtual status_t send_command( + node_id node, OMX_COMMANDTYPE cmd, OMX_S32 param) = 0; + + virtual status_t get_parameter( + node_id node, OMX_INDEXTYPE index, + void *params, size_t size) = 0; + + virtual status_t set_parameter( + node_id node, OMX_INDEXTYPE index, + const void *params, size_t size) = 0; + + virtual status_t use_buffer( + node_id node, OMX_U32 port_index, const sp<IMemory> ¶ms, + buffer_id *buffer) = 0; + + virtual status_t allocate_buffer( + node_id node, OMX_U32 port_index, size_t size, + buffer_id *buffer) = 0; + + virtual status_t allocate_buffer_with_backup( + node_id node, OMX_U32 port_index, const sp<IMemory> ¶ms, + buffer_id *buffer) = 0; + + virtual status_t free_buffer( + node_id node, OMX_U32 port_index, buffer_id buffer) = 0; + +#if !IOMX_USES_SOCKETS + virtual status_t observe_node( + node_id node, const sp<IOMXObserver> &observer) = 0; + + virtual void fill_buffer(node_id node, buffer_id buffer) = 0; + + virtual void empty_buffer( + node_id node, + buffer_id buffer, + OMX_U32 range_offset, OMX_U32 range_length, + OMX_U32 flags, OMX_TICKS timestamp) = 0; +#endif +}; + +struct omx_message { + enum { + EVENT, + EMPTY_BUFFER_DONE, + FILL_BUFFER_DONE, + +#if IOMX_USES_SOCKETS + EMPTY_BUFFER, + FILL_BUFFER, + SEND_COMMAND, + DISCONNECT, + DISCONNECTED, +#endif + + // reserved for OMXDecoder use. + START, + INITIAL_FILL_BUFFER, + + // reserved for OMXObserver use. + QUIT_OBSERVER, + } type; + + union { + // if type == EVENT + struct { + IOMX::node_id node; + OMX_EVENTTYPE event; + OMX_U32 data1; + OMX_U32 data2; + } event_data; + + // if type == EMPTY_BUFFER_DONE || type == FILL_BUFFER + // || type == INITIAL_FILL_BUFFER + struct { + IOMX::node_id node; + IOMX::buffer_id buffer; + } buffer_data; + + // if type == EMPTY_BUFFER || type == FILL_BUFFER_DONE + struct { + IOMX::node_id node; + IOMX::buffer_id buffer; + OMX_U32 range_offset; + OMX_U32 range_length; + OMX_U32 flags; + OMX_TICKS timestamp; + OMX_PTR platform_private; // ignored if type == EMPTY_BUFFER + } extended_buffer_data; + + // if type == SEND_COMMAND + struct { + IOMX::node_id node; + OMX_COMMANDTYPE cmd; + OMX_S32 param; + } send_command_data; + + } u; +}; + +class IOMXObserver : public IInterface { +public: + DECLARE_META_INTERFACE(OMXObserver); + + virtual void on_message(const omx_message &msg) = 0; +}; + +//////////////////////////////////////////////////////////////////////////////// + +class BnOMX : public BnInterface<IOMX> { +public: + virtual status_t onTransact( + uint32_t code, const Parcel &data, Parcel *reply, + uint32_t flags = 0); +}; + +class BnOMXObserver : public BnInterface<IOMXObserver> { +public: + virtual status_t onTransact( + uint32_t code, const Parcel &data, Parcel *reply, + uint32_t flags = 0); +}; + +} // namespace android + +#endif // ANDROID_IOMX_H_ diff --git a/include/media/MediaPlayerInterface.h b/include/media/MediaPlayerInterface.h index 21600b2..d1933f6 100644 --- a/include/media/MediaPlayerInterface.h +++ b/include/media/MediaPlayerInterface.h @@ -32,7 +32,8 @@ class Parcel; enum player_type { PV_PLAYER = 1, SONIVOX_PLAYER = 2, - VORBIS_PLAYER = 3 + VORBIS_PLAYER = 3, + STAGEFRIGHT_PLAYER = 4 }; @@ -51,6 +52,9 @@ public: // AudioSink: abstraction layer for audio output class AudioSink : public RefBase { public: + typedef void (*AudioCallback)( + AudioSink *audioSink, void *buffer, size_t size, void *cookie); + virtual ~AudioSink() {} virtual bool ready() const = 0; // audio output is open and ready virtual bool realtime() const = 0; // audio output is real-time output @@ -60,7 +64,17 @@ public: virtual ssize_t frameSize() const = 0; virtual uint32_t latency() const = 0; virtual float msecsPerFrame() const = 0; - virtual status_t open(uint32_t sampleRate, int channelCount, int format=AudioSystem::PCM_16_BIT, int bufferCount=DEFAULT_AUDIOSINK_BUFFERCOUNT) = 0; + + // If no callback is specified, use the "write" API below to submit + // audio data. Otherwise return a full buffer of audio data on each + // callback. + virtual status_t open( + uint32_t sampleRate, int channelCount, + int format=AudioSystem::PCM_16_BIT, + int bufferCount=DEFAULT_AUDIOSINK_BUFFERCOUNT, + AudioCallback cb = NULL, + void *cookie = NULL) = 0; + virtual void start() = 0; virtual ssize_t write(const void* buffer, size_t size) = 0; virtual void stop() = 0; diff --git a/include/media/stagefright/AudioPlayer.h b/include/media/stagefright/AudioPlayer.h new file mode 100644 index 0000000..0f2e528 --- /dev/null +++ b/include/media/stagefright/AudioPlayer.h @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2009 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. + */ + +#ifndef AUDIO_PLAYER_H_ + +#define AUDIO_PLAYER_H_ + +#include <media/MediaPlayerInterface.h> +#include <media/stagefright/MediaBuffer.h> +#include <media/stagefright/TimeSource.h> +#include <utils/threads.h> + +namespace android { + +class MediaSource; +class AudioTrack; + +class AudioPlayer : public TimeSource { +public: + AudioPlayer(const sp<MediaPlayerBase::AudioSink> &audioSink); + ~AudioPlayer(); + + // Caller retains ownership of "source". + void setSource(MediaSource *source); + + // Return time in us. + virtual int64_t getRealTimeUs(); + + void start(); + + void pause(); + void resume(); + + void stop(); + + // Returns the timestamp of the last buffer played (in us). + int64_t getMediaTimeUs(); + + // Returns true iff a mapping is established, i.e. the AudioPlayer + // has played at least one frame of audio. + bool getMediaTimeMapping(int64_t *realtime_us, int64_t *mediatime_us); + + status_t seekTo(int64_t time_us); + +private: + MediaSource *mSource; + AudioTrack *mAudioTrack; + + MediaBuffer *mInputBuffer; + + int mSampleRate; + int64_t mLatencyUs; + size_t mFrameSize; + + Mutex mLock; + int64_t mNumFramesPlayed; + + int64_t mPositionTimeMediaUs; + int64_t mPositionTimeRealUs; + + bool mSeeking; + int64_t mSeekTimeUs; + + bool mStarted; + + sp<MediaPlayerBase::AudioSink> mAudioSink; + + static void AudioCallback(int event, void *user, void *info); + void AudioCallback(int event, void *info); + + static void AudioSinkCallback( + MediaPlayerBase::AudioSink *audioSink, + void *data, size_t size, void *me); + + void fillBuffer(void *data, size_t size); + + int64_t getRealTimeUsLocked() const; + + AudioPlayer(const AudioPlayer &); + AudioPlayer &operator=(const AudioPlayer &); +}; + +} // namespace android + +#endif // AUDIO_PLAYER_H_ diff --git a/include/media/stagefright/AudioSource.h b/include/media/stagefright/AudioSource.h new file mode 100644 index 0000000..e129958 --- /dev/null +++ b/include/media/stagefright/AudioSource.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2009 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. + */ + +#ifndef AUDIO_SOURCE_H_ + +#define AUDIO_SOURCE_H_ + +#include <media/stagefright/MediaSource.h> + +namespace android { + +class AudioRecord; + +class AudioSource { +public: + AudioSource(int inputSource); + virtual ~AudioSource(); + + status_t initCheck() const; + + virtual status_t start(MetaData *params = NULL); + virtual status_t stop(); + virtual sp<MetaData> getFormat(); + + virtual status_t read( + MediaBuffer **buffer, const ReadOptions *options = NULL); + +private: + AudioRecord *mRecord; + status_t mInitCheck; + + AudioSource(const AudioSource &); + AudioSource &operator=(const AudioSource &); +}; + +} // namespace android + +#endif // AUDIO_SOURCE_H_ diff --git a/include/media/stagefright/CachingDataSource.h b/include/media/stagefright/CachingDataSource.h new file mode 100644 index 0000000..e275cb4 --- /dev/null +++ b/include/media/stagefright/CachingDataSource.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2009 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. + */ + +#ifndef CACHING_DATASOURCE_H_ + +#define CACHING_DATASOURCE_H_ + +#include <media/stagefright/DataSource.h> +#include <media/stagefright/MediaErrors.h> +#include <utils/threads.h> + +namespace android { + +class CachingDataSource : public DataSource { +public: + // Assumes ownership of "source". + CachingDataSource(DataSource *source, size_t pageSize, int numPages); + virtual ~CachingDataSource(); + + status_t InitCheck() const; + + virtual ssize_t read_at(off_t offset, void *data, size_t size); + +private: + struct Page { + Page *mPrev, *mNext; + off_t mOffset; + size_t mLength; + void *mData; + }; + + DataSource *mSource; + void *mData; + size_t mPageSize; + Page *mFirst, *mLast; + + Page *allocate_page(); + + Mutex mLock; + + CachingDataSource(const CachingDataSource &); + CachingDataSource &operator=(const CachingDataSource &); +}; + +} // namespace android + +#endif // CACHING_DATASOURCE_H_ diff --git a/include/media/stagefright/CameraSource.h b/include/media/stagefright/CameraSource.h new file mode 100644 index 0000000..7042e1a --- /dev/null +++ b/include/media/stagefright/CameraSource.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2009 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. + */ + +#ifndef CAMERA_SOURCE_H_ + +#define CAMERA_SOURCE_H_ + +#include <media/stagefright/MediaBuffer.h> +#include <media/stagefright/MediaSource.h> +#include <utils/List.h> +#include <utils/RefBase.h> +#include <utils/threads.h> + +namespace android { + +class ICamera; +class ICameraClient; +class IMemory; + +class CameraSource : public MediaSource, + public MediaBufferObserver { +public: + static CameraSource *Create(); + + virtual ~CameraSource(); + + virtual status_t start(MetaData *params = NULL); + virtual status_t stop(); + + virtual sp<MetaData> getFormat(); + + virtual status_t read( + MediaBuffer **buffer, const ReadOptions *options = NULL); + + virtual void notifyCallback(int32_t msgType, int32_t ext1, int32_t ext2); + virtual void dataCallback(int32_t msgType, const sp<IMemory>& data); + + virtual void signalBufferReturned(MediaBuffer *buffer); + +private: + CameraSource(const sp<ICamera> &camera, const sp<ICameraClient> &client); + + sp<ICamera> mCamera; + sp<ICameraClient> mCameraClient; + + Mutex mLock; + Condition mFrameAvailableCondition; + List<sp<IMemory> > mFrames; + + int mNumFrames; + bool mStarted; + + CameraSource(const CameraSource &); + CameraSource &operator=(const CameraSource &); +}; + +} // namespace android + +#endif // CAMERA_SOURCE_H_ diff --git a/include/media/stagefright/DataSource.h b/include/media/stagefright/DataSource.h new file mode 100644 index 0000000..31eea27 --- /dev/null +++ b/include/media/stagefright/DataSource.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2009 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. + */ + +#ifndef DATA_SOURCE_H_ + +#define DATA_SOURCE_H_ + +#include <sys/types.h> + +#include <utils/Errors.h> +#include <utils/List.h> +#include <utils/threads.h> + +namespace android { + +class String8; + +class DataSource { +public: + DataSource() {} + virtual ~DataSource() {} + + virtual ssize_t read_at(off_t offset, void *data, size_t size) = 0; + + // May return ERROR_UNSUPPORTED. + virtual status_t getSize(off_t *size); + + //////////////////////////////////////////////////////////////////////////// + + bool sniff(String8 *mimeType, float *confidence); + + typedef bool (*SnifferFunc)( + DataSource *source, String8 *mimeType, float *confidence); + + static void RegisterSniffer(SnifferFunc func); + static void RegisterDefaultSniffers(); + +private: + static Mutex gSnifferMutex; + static List<SnifferFunc> gSniffers; + + DataSource(const DataSource &); + DataSource &operator=(const DataSource &); +}; + +} // namespace android + +#endif // DATA_SOURCE_H_ diff --git a/include/media/stagefright/ESDS.h b/include/media/stagefright/ESDS.h new file mode 100644 index 0000000..01bcd18 --- /dev/null +++ b/include/media/stagefright/ESDS.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2009 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. + */ + +#ifndef ESDS_H_ + +#define ESDS_H_ + +#include <stdint.h> + +#include <media/stagefright/MediaErrors.h> + +namespace android { + +class ESDS { +public: + ESDS(const void *data, size_t size); + ~ESDS(); + + status_t InitCheck() const; + + status_t getCodecSpecificInfo(const void **data, size_t *size) const; + +private: + enum { + kTag_ESDescriptor = 0x03, + kTag_DecoderConfigDescriptor = 0x04, + kTag_DecoderSpecificInfo = 0x05 + }; + + uint8_t *mData; + size_t mSize; + + status_t mInitCheck; + + size_t mDecoderSpecificOffset; + size_t mDecoderSpecificLength; + + status_t skipDescriptorHeader( + size_t offset, size_t size, + uint8_t *tag, size_t *data_offset, size_t *data_size) const; + + status_t parse(); + status_t parseESDescriptor(size_t offset, size_t size); + status_t parseDecoderConfigDescriptor(size_t offset, size_t size); + + ESDS(const ESDS &); + ESDS &operator=(const ESDS &); +}; + +} // namespace android +#endif // ESDS_H_ diff --git a/include/media/stagefright/FileSource.h b/include/media/stagefright/FileSource.h new file mode 100644 index 0000000..ccbe0ef --- /dev/null +++ b/include/media/stagefright/FileSource.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2009 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. + */ + +#ifndef FILE_SOURCE_H_ + +#define FILE_SOURCE_H_ + +#include <stdio.h> + +#include <media/stagefright/DataSource.h> +#include <media/stagefright/MediaErrors.h> +#include <utils/threads.h> + +namespace android { + +class FileSource : public DataSource { +public: + FileSource(const char *filename); + virtual ~FileSource(); + + status_t InitCheck() const; + + virtual ssize_t read_at(off_t offset, void *data, size_t size); + +private: + FILE *mFile; + Mutex mLock; + + FileSource(const FileSource &); + FileSource &operator=(const FileSource &); +}; + +} // namespace android + +#endif // FILE_SOURCE_H_ + diff --git a/include/media/stagefright/HTTPDataSource.h b/include/media/stagefright/HTTPDataSource.h new file mode 100644 index 0000000..0587c7c --- /dev/null +++ b/include/media/stagefright/HTTPDataSource.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2009 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. + */ + +#ifndef HTTP_DATASOURCE_H_ + +#define HTTP_DATASOURCE_H_ + +#include <media/stagefright/DataSource.h> +#include <media/stagefright/HTTPStream.h> + +namespace android { + +class HTTPDataSource : public DataSource { +public: + HTTPDataSource(const char *host, int port, const char *path); + HTTPDataSource(const char *uri); + + virtual ~HTTPDataSource(); + + // XXXandih + status_t InitCheck() const { return OK; } + + virtual ssize_t read_at(off_t offset, void *data, size_t size); + +private: + enum { + kBufferSize = 64 * 1024 + }; + + HTTPStream mHttp; + char *mHost; + int mPort; + char *mPath; + + void *mBuffer; + size_t mBufferLength; + off_t mBufferOffset; + + HTTPDataSource(const HTTPDataSource &); + HTTPDataSource &operator=(const HTTPDataSource &); +}; + +} // namespace android + +#endif // HTTP_DATASOURCE_H_ + diff --git a/include/media/stagefright/HTTPStream.h b/include/media/stagefright/HTTPStream.h new file mode 100644 index 0000000..3d0d67a --- /dev/null +++ b/include/media/stagefright/HTTPStream.h @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2009 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. + */ + +#ifndef HTTP_STREAM_H_ + +#define HTTP_STREAM_H_ + +#include <sys/types.h> + +#include <media/stagefright/MediaErrors.h> +#include <media/stagefright/string.h> +#include <utils/KeyedVector.h> + +namespace android { + +class HTTPStream { +public: + HTTPStream(); + ~HTTPStream(); + + status_t connect(const char *server, int port = 80); + status_t disconnect(); + + status_t send(const char *data, size_t size); + + // Assumes data is a '\0' terminated string. + status_t send(const char *data); + + // Receive up to "size" bytes of data. + ssize_t receive(void *data, size_t size); + + status_t receive_header(int *http_status); + + // The header key used to retrieve the status line. + static const char *kStatusKey; + + bool find_header_value( + const string &key, string *value) const; + +private: + enum State { + READY, + CONNECTED + }; + + State mState; + int mSocket; + + KeyedVector<string, string> mHeaders; + + // Receive a line of data terminated by CRLF, line will be '\0' terminated + // _excluding_ the termianting CRLF. + status_t receive_line(char *line, size_t size); + + HTTPStream(const HTTPStream &); + HTTPStream &operator=(const HTTPStream &); +}; + +} // namespace android + +#endif // HTTP_STREAM_H_ diff --git a/include/media/stagefright/MP3Extractor.h b/include/media/stagefright/MP3Extractor.h new file mode 100644 index 0000000..09cfb70 --- /dev/null +++ b/include/media/stagefright/MP3Extractor.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2009 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. + */ + +#ifndef MP3_EXTRACTOR_H_ + +#define MP3_EXTRACTOR_H_ + +#include <media/stagefright/MediaExtractor.h> + +namespace android { + +class DataSource; +class String8; + +class MP3Extractor : public MediaExtractor { +public: + // Extractor assumes ownership of "source". + MP3Extractor(DataSource *source); + + ~MP3Extractor(); + + status_t countTracks(int *num_tracks); + status_t getTrack(int index, MediaSource **source); + sp<MetaData> getTrackMetaData(int index); + +private: + DataSource *mDataSource; + off_t mFirstFramePos; + sp<MetaData> mMeta; + uint32_t mFixedHeader; + + MP3Extractor(const MP3Extractor &); + MP3Extractor &operator=(const MP3Extractor &); +}; + +bool SniffMP3(DataSource *source, String8 *mimeType, float *confidence); + +} // namespace android + +#endif // MP3_EXTRACTOR_H_ diff --git a/include/media/stagefright/MPEG4Extractor.h b/include/media/stagefright/MPEG4Extractor.h new file mode 100644 index 0000000..51a7e82 --- /dev/null +++ b/include/media/stagefright/MPEG4Extractor.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2009 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. + */ + +#ifndef MPEG4_EXTRACTOR_H_ + +#define MPEG4_EXTRACTOR_H_ + +#include <media/stagefright/MediaExtractor.h> + +namespace android { + +class DataSource; +class SampleTable; +class String8; + +class MPEG4Extractor : public MediaExtractor { +public: + // Extractor assumes ownership of "source". + MPEG4Extractor(DataSource *source); + ~MPEG4Extractor(); + + status_t countTracks(int *num_tracks); + status_t getTrack(int index, MediaSource **source); + sp<MetaData> getTrackMetaData(int index); + +private: + struct Track { + Track *next; + sp<MetaData> meta; + uint32_t timescale; + SampleTable *sampleTable; + }; + + DataSource *mDataSource; + bool mHaveMetadata; + + Track *mFirstTrack, *mLastTrack; + + uint32_t mHandlerType; + + status_t readMetaData(); + status_t parseChunk(off_t *offset, int depth); + + MPEG4Extractor(const MPEG4Extractor &); + MPEG4Extractor &operator=(const MPEG4Extractor &); +}; + +bool SniffMPEG4(DataSource *source, String8 *mimeType, float *confidence); + +} // namespace android + +#endif // MPEG4_EXTRACTOR_H_ diff --git a/include/media/stagefright/MPEG4Writer.h b/include/media/stagefright/MPEG4Writer.h new file mode 100644 index 0000000..40d6127 --- /dev/null +++ b/include/media/stagefright/MPEG4Writer.h @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2009 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. + */ + +#ifndef MPEG4_WRITER_H_ + +#define MPEG4_WRITER_H_ + +#include <stdio.h> + +#include <utils/List.h> +#include <utils/RefBase.h> +#include <utils/threads.h> + +namespace android { + +class MediaBuffer; +class MediaSource; +class MetaData; + +class MPEG4Writer { +public: + MPEG4Writer(const char *filename); + ~MPEG4Writer(); + + // Caller retains ownership of both meta and source. + void addSource(const sp<MetaData> &meta, MediaSource *source); + void start(); + void stop(); + + void beginBox(const char *fourcc); + void writeInt8(int8_t x); + void writeInt16(int16_t x); + void writeInt32(int32_t x); + void writeInt64(int64_t x); + void writeCString(const char *s); + void writeFourcc(const char *fourcc); + void write(const void *data, size_t size); + void endBox(); + +private: + class Track; + + FILE *mFile; + off_t mOffset; + off_t mMdatOffset; + Mutex mLock; + + List<Track *> mTracks; + + List<off_t> mBoxes; + + off_t addSample(MediaBuffer *buffer); + + MPEG4Writer(const MPEG4Writer &); + MPEG4Writer &operator=(const MPEG4Writer &); +}; + +} // namespace android + +#endif // MPEG4_WRITER_H_ diff --git a/include/media/stagefright/MediaBuffer.h b/include/media/stagefright/MediaBuffer.h new file mode 100644 index 0000000..c72ed66 --- /dev/null +++ b/include/media/stagefright/MediaBuffer.h @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2009 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. + */ + +#ifndef MEDIA_BUFFER_H_ + +#define MEDIA_BUFFER_H_ + +#include <pthread.h> + +#include <utils/Errors.h> +#include <utils/RefBase.h> + +namespace android { + +class MediaBuffer; +class MediaBufferObserver; +class MetaData; + +class MediaBufferObserver { +public: + MediaBufferObserver() {} + virtual ~MediaBufferObserver() {} + + virtual void signalBufferReturned(MediaBuffer *buffer) = 0; + +private: + MediaBufferObserver(const MediaBufferObserver &); + MediaBufferObserver &operator=(const MediaBufferObserver &); +}; + +class MediaBuffer { +public: + // The underlying data remains the responsibility of the caller! + MediaBuffer(void *data, size_t size); + + MediaBuffer(size_t size); + + // Decrements the reference count and returns the buffer to its + // associated MediaBufferGroup if the reference count drops to 0. + void release(); + + // Increments the reference count. + void add_ref(); + + void *data() const; + size_t size() const; + + size_t range_offset() const; + size_t range_length() const; + + void set_range(size_t offset, size_t length); + + sp<MetaData> meta_data(); + + // Clears meta data and resets the range to the full extent. + void reset(); + + void setObserver(MediaBufferObserver *group); + + // Returns a clone of this MediaBuffer increasing its reference count. + // The clone references the same data but has its own range and + // MetaData. + MediaBuffer *clone(); + +protected: + virtual ~MediaBuffer(); + +private: + friend class MediaBufferGroup; + friend class OMXDecoder; + + // For use by OMXDecoder, reference count must be 1, drop reference + // count to 0 without signalling the observer. + void claim(); + + MediaBufferObserver *mObserver; + MediaBuffer *mNextBuffer; + int mRefCount; + + void *mData; + size_t mSize, mRangeOffset, mRangeLength; + + bool mOwnsData; + + sp<MetaData> mMetaData; + + MediaBuffer *mOriginal; + + void setNextBuffer(MediaBuffer *buffer); + MediaBuffer *nextBuffer(); + + int refcount() const; + + MediaBuffer(const MediaBuffer &); + MediaBuffer &operator=(const MediaBuffer &); +}; + +} // namespace android + +#endif // MEDIA_BUFFER_H_ diff --git a/include/media/stagefright/MediaBufferGroup.h b/include/media/stagefright/MediaBufferGroup.h new file mode 100644 index 0000000..e95a9c2 --- /dev/null +++ b/include/media/stagefright/MediaBufferGroup.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2009 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. + */ + +#ifndef MEDIA_BUFFER_GROUP_H_ + +#define MEDIA_BUFFER_GROUP_H_ + +#include <utils/Errors.h> +#include <utils/threads.h> + +namespace android { + +class MediaBuffer; +class MetaData; + +class MediaBufferGroup : public MediaBufferObserver { +public: + MediaBufferGroup(); + ~MediaBufferGroup(); + + void add_buffer(MediaBuffer *buffer); + + // Blocks until a buffer is available and returns it to the caller, + // the returned buffer will have a reference count of 1. + status_t acquire_buffer(MediaBuffer **buffer); + +protected: + virtual void signalBufferReturned(MediaBuffer *buffer); + +private: + friend class MediaBuffer; + + Mutex mLock; + Condition mCondition; + + MediaBuffer *mFirstBuffer, *mLastBuffer; + + MediaBufferGroup(const MediaBufferGroup &); + MediaBufferGroup &operator=(const MediaBufferGroup &); +}; + +} // namespace android + +#endif // MEDIA_BUFFER_GROUP_H_ diff --git a/include/media/stagefright/MediaErrors.h b/include/media/stagefright/MediaErrors.h new file mode 100644 index 0000000..2bb0ed6 --- /dev/null +++ b/include/media/stagefright/MediaErrors.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2009 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. + */ + +#ifndef MEDIA_ERRORS_H_ + +#define MEDIA_ERRORS_H_ + +#include <utils/Errors.h> + +namespace android { + +enum { + MEDIA_ERROR_BASE = -1000, + + ERROR_ALREADY_CONNECTED = MEDIA_ERROR_BASE, + ERROR_NOT_CONNECTED = MEDIA_ERROR_BASE - 1, + ERROR_UNKNOWN_HOST = MEDIA_ERROR_BASE - 2, + ERROR_CANNOT_CONNECT = MEDIA_ERROR_BASE - 3, + ERROR_IO = MEDIA_ERROR_BASE - 4, + ERROR_CONNECTION_LOST = MEDIA_ERROR_BASE - 5, + ERROR_MALFORMED = MEDIA_ERROR_BASE - 7, + ERROR_OUT_OF_RANGE = MEDIA_ERROR_BASE - 8, + ERROR_BUFFER_TOO_SMALL = MEDIA_ERROR_BASE - 9, + ERROR_UNSUPPORTED = MEDIA_ERROR_BASE - 10, + ERROR_END_OF_STREAM = MEDIA_ERROR_BASE - 11, +}; + +} // namespace android + +#endif // MEDIA_ERRORS_H_ diff --git a/include/media/stagefright/MediaExtractor.h b/include/media/stagefright/MediaExtractor.h new file mode 100644 index 0000000..38f8e5b --- /dev/null +++ b/include/media/stagefright/MediaExtractor.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2009 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. + */ + +#ifndef MEDIA_EXTRACTOR_H_ + +#define MEDIA_EXTRACTOR_H_ + +#include <utils/RefBase.h> + +namespace android { + +class DataSource; +class MediaSource; +class MetaData; + +class MediaExtractor { +public: + static MediaExtractor *Create(DataSource *source, const char *mime = NULL); + + virtual ~MediaExtractor() {} + + virtual status_t countTracks(int *num_tracks) = 0; + virtual status_t getTrack(int index, MediaSource **source) = 0; + virtual sp<MetaData> getTrackMetaData(int index) = 0; + +protected: + MediaExtractor() {} + +private: + MediaExtractor(const MediaExtractor &); + MediaExtractor &operator=(const MediaExtractor &); +}; + +} // namespace android + +#endif // MEDIA_EXTRACTOR_H_ diff --git a/include/media/stagefright/MediaPlayerImpl.h b/include/media/stagefright/MediaPlayerImpl.h new file mode 100644 index 0000000..c48400c --- /dev/null +++ b/include/media/stagefright/MediaPlayerImpl.h @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2009 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. + */ + +#ifndef MEDIA_PLAYER_IMPL_H_ + +#define MEDIA_PLAYER_IMPL_H_ + +#include <pthread.h> + +#include <media/MediaPlayerInterface.h> +#include <media/stagefright/OMXClient.h> +#include <utils/RefBase.h> +#include <utils/threads.h> + +namespace android { + +class AudioPlayer; +class ISurface; +class MediaExtractor; +class MediaBuffer; +class MediaSource; +class MemoryHeapPmem; +class MetaData; +class OMXDecoder; +class Surface; +class TimeSource; +class VideoRenderer; + +class MediaPlayerImpl { +public: + MediaPlayerImpl(const char *uri); + + status_t initCheck() const; + + // Assumes ownership of "fd". + MediaPlayerImpl(int fd, int64_t offset, int64_t length); + + ~MediaPlayerImpl(); + + void play(); + void pause(); + bool isPlaying() const; + + void setSurface(const sp<Surface> &surface); + void setISurface(const sp<ISurface> &isurface); + + void setAudioSink(const sp<MediaPlayerBase::AudioSink> &audioSink); + + int32_t getWidth() const { return mVideoWidth; } + int32_t getHeight() const { return mVideoHeight; } + + int64_t getDuration(); + int64_t getPosition(); + status_t seekTo(int64_t time); + +private: + status_t mInitCheck; + + OMXClient mClient; + + MediaExtractor *mExtractor; + + TimeSource *mTimeSource; + + MediaSource *mAudioSource; + OMXDecoder *mAudioDecoder; + AudioPlayer *mAudioPlayer; + + MediaSource *mVideoSource; + MediaSource *mVideoDecoder; + int32_t mVideoWidth, mVideoHeight; + int64_t mVideoPosition; + + int64_t mDuration; + + bool mPlaying; + bool mPaused; + + int64_t mTimeSourceDeltaUs; + + sp<Surface> mSurface; + sp<ISurface> mISurface; + VideoRenderer *mRenderer; + + sp<MediaPlayerBase::AudioSink> mAudioSink; + + Mutex mLock; + pthread_t mVideoThread; + + bool mSeeking; + int64_t mSeekTimeUs; + + size_t mFrameSize; + bool mUseSoftwareColorConversion; + + void init(); + + static void *VideoWrapper(void *me); + void videoEntry(); + + void setAudioSource(MediaSource *source); + void setVideoSource(MediaSource *source); + + MediaSource *makeShoutcastSource(const char *path); + + void displayOrDiscardFrame(MediaBuffer *buffer, int64_t pts_us); + void populateISurface(); + void depopulateISurface(); + void sendFrameToISurface(MediaBuffer *buffer); + + void stop(); + + MediaPlayerImpl(const MediaPlayerImpl &); + MediaPlayerImpl &operator=(const MediaPlayerImpl &); +}; + +} // namespace android + +#endif // MEDIA_PLAYER_IMPL_H_ diff --git a/include/media/stagefright/MediaSource.h b/include/media/stagefright/MediaSource.h new file mode 100644 index 0000000..eb07f68 --- /dev/null +++ b/include/media/stagefright/MediaSource.h @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2009 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. + */ + +#ifndef MEDIA_SOURCE_H_ + +#define MEDIA_SOURCE_H_ + +#include <sys/types.h> + +#include <utils/RefBase.h> + +namespace android { + +class MediaBuffer; +class MetaData; + +struct MediaSource { + MediaSource(); + virtual ~MediaSource(); + + // To be called before any other methods on this object, except + // getFormat(). + virtual status_t start(MetaData *params = NULL) = 0; + + // Any blocking read call returns immediately with a result of NO_INIT. + // It is an error to call any methods other than start after this call + // returns. Any buffers the object may be holding onto at the time of + // the stop() call are released. + // Also, it is imperative that any buffers output by this object and + // held onto by callers be released before a call to stop() !!! + virtual status_t stop() = 0; + + // Returns the format of the data output by this media source. + virtual sp<MetaData> getFormat() = 0; + + struct ReadOptions; + + // Returns a new buffer of data. Call blocks until a + // buffer is available, an error is encountered of the end of the stream + // is reached. + // End of stream is signalled by a result of ERROR_END_OF_STREAM. + virtual status_t read( + MediaBuffer **buffer, const ReadOptions *options = NULL) = 0; + + // Options that modify read() behaviour. The default is to + // a) not request a seek + // b) not be late, i.e. lateness_us = 0 + struct ReadOptions { + ReadOptions(); + + // Reset everything back to defaults. + void reset(); + + void setSeekTo(int64_t time_us); + void clearSeekTo(); + bool getSeekTo(int64_t *time_us) const; + + void setLateBy(int64_t lateness_us); + int64_t getLateBy() const; + + private: + enum Options { + kSeekTo_Option = 1, + }; + + uint32_t mOptions; + int64_t mSeekTimeUs; + int64_t mLatenessUs; + }; + +private: + MediaSource(const MediaSource &); + MediaSource &operator=(const MediaSource &); +}; + +} // namespace android + +#endif // MEDIA_SOURCE_H_ diff --git a/include/media/stagefright/MetaData.h b/include/media/stagefright/MetaData.h new file mode 100644 index 0000000..04805da --- /dev/null +++ b/include/media/stagefright/MetaData.h @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2009 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. + */ + +#ifndef META_DATA_H_ + +#define META_DATA_H_ + +#include <sys/types.h> + +#include <stdint.h> + +#include <utils/RefBase.h> +#include <utils/KeyedVector.h> + +namespace android { + +enum { + kKeyMIMEType = 'mime', + kKeyWidth = 'widt', + kKeyHeight = 'heig', + kKeyChannelCount = '#chn', + kKeySampleRate = 'srte', + kKeyBitRate = 'brte', + kKeyESDS = 'esds', + kKeyAVCC = 'avcc', + kKeyTimeUnits = '#tim', + kKeyTimeScale = 'scal', + kKeyNeedsNALFraming = 'NALf', + kKeyIsSyncFrame = 'sync', + kKeyDuration = 'dura', + kKeyColorFormat = 'colf', + kKeyPlatformPrivate = 'priv', + kKeyDecoderComponent = 'decC', +}; + +enum { + kTypeESDS = 'esds', + kTypeAVCC = 'avcc', +}; + +class MetaData : public RefBase { +public: + MetaData(); + MetaData(const MetaData &from); + + enum Type { + TYPE_NONE = 'none', + TYPE_C_STRING = 'cstr', + TYPE_INT32 = 'in32', + TYPE_FLOAT = 'floa', + TYPE_POINTER = 'ptr ', + }; + + void clear(); + bool remove(uint32_t key); + + bool setCString(uint32_t key, const char *value); + bool setInt32(uint32_t key, int32_t value); + bool setFloat(uint32_t key, float value); + bool setPointer(uint32_t key, void *value); + + bool findCString(uint32_t key, const char **value); + bool findInt32(uint32_t key, int32_t *value); + bool findFloat(uint32_t key, float *value); + bool findPointer(uint32_t key, void **value); + + bool setData(uint32_t key, uint32_t type, const void *data, size_t size); + + bool findData(uint32_t key, uint32_t *type, + const void **data, size_t *size) const; + +protected: + virtual ~MetaData(); + +private: + struct typed_data { + typed_data(); + ~typed_data(); + + typed_data(const MetaData::typed_data &); + typed_data &operator=(const MetaData::typed_data &); + + void clear(); + void setData(uint32_t type, const void *data, size_t size); + void getData(uint32_t *type, const void **data, size_t *size) const; + + private: + uint32_t mType; + size_t mSize; + + union { + void *ext_data; + float reservoir; + } u; + + bool usesReservoir() const { + return mSize <= sizeof(u.reservoir); + } + + void allocateStorage(size_t size); + void freeStorage(); + + void *storage() { + return usesReservoir() ? &u.reservoir : u.ext_data; + } + + const void *storage() const { + return usesReservoir() ? &u.reservoir : u.ext_data; + } + }; + + KeyedVector<uint32_t, typed_data> mItems; + + // MetaData &operator=(const MetaData &); +}; + +} // namespace android + +#endif // META_DATA_H_ diff --git a/include/media/stagefright/MmapSource.h b/include/media/stagefright/MmapSource.h new file mode 100644 index 0000000..a8bd57f --- /dev/null +++ b/include/media/stagefright/MmapSource.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2009 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. + */ + +#ifndef MMAP_SOURCE_H_ + +#define MMAP_SOURCE_H_ + +#include <media/stagefright/DataSource.h> +#include <media/stagefright/MediaErrors.h> + +namespace android { + +class MmapSource : public DataSource { +public: + MmapSource(const char *filename); + + // Assumes ownership of "fd". + MmapSource(int fd, int64_t offset, int64_t length); + + virtual ~MmapSource(); + + status_t InitCheck() const; + + virtual ssize_t read_at(off_t offset, void *data, size_t size); + virtual status_t getSize(off_t *size); + +private: + int mFd; + void *mBase; + size_t mSize; + + MmapSource(const MmapSource &); + MmapSource &operator=(const MmapSource &); +}; + +} // namespace android + +#endif // MMAP_SOURCE_H_ + diff --git a/include/media/stagefright/OMXClient.h b/include/media/stagefright/OMXClient.h new file mode 100644 index 0000000..454c38b --- /dev/null +++ b/include/media/stagefright/OMXClient.h @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2009 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. + */ + +#ifndef OMX_CLIENT_H_ + +#define OMX_CLIENT_H_ + +#include <media/IOMX.h> + +#include <utils/KeyedVector.h> +#include <utils/List.h> +#include <utils/threads.h> + +namespace android { + +class OMXObserver { +public: + OMXObserver(); + virtual ~OMXObserver(); + + void postMessage(const omx_message &msg); + +protected: + virtual void onOMXMessage(const omx_message &msg) = 0; + +private: + friend class OMXClient; + + pthread_t mThread; + Mutex mLock; + Condition mQueueNotEmpty; + List<omx_message> mQueue; + + void start(); + void stop(); + + static void *ThreadWrapper(void *me); + void threadEntry(); + + OMXObserver(const OMXObserver &); + OMXObserver &operator=(const OMXObserver &); +}; + +class OMXClient; + +class OMXClientReflector : public BnOMXObserver { +public: + OMXClientReflector(OMXClient *client); + + virtual void on_message(const omx_message &msg); + void reset(); + +private: + OMXClient *mClient; + + OMXClientReflector(const OMXClientReflector &); + OMXClientReflector &operator=(const OMXClientReflector &); +}; + +class OMXClient { +public: + friend class OMXClientReflector; + + OMXClient(); + ~OMXClient(); + + status_t connect(); + void disconnect(); + + sp<IOMX> interface() { + return mOMX; + } + + status_t registerObserver(IOMX::node_id node, OMXObserver *observer); + void unregisterObserver(IOMX::node_id node); + + status_t fillBuffer(IOMX::node_id node, IOMX::buffer_id buffer); + + status_t emptyBuffer( + IOMX::node_id node, IOMX::buffer_id buffer, + OMX_U32 range_offset, OMX_U32 range_length, + OMX_U32 flags, OMX_TICKS timestamp); + + status_t send_command( + IOMX::node_id node, OMX_COMMANDTYPE cmd, OMX_S32 param); + +private: + sp<IOMX> mOMX; + + int mSock; + Mutex mLock; + pthread_t mThread; + + KeyedVector<IOMX::node_id, OMXObserver *> mObservers; + + sp<OMXClientReflector> mReflector; + +#if IOMX_USES_SOCKETS + static void *ThreadWrapper(void *me); + void threadEntry(); +#endif + + bool onOMXMessage(const omx_message &msg); + + OMXClient(const OMXClient &); + OMXClient &operator=(const OMXClient &); +}; + +} // namespace android + +#endif // OMX_CLIENT_H_ diff --git a/include/media/stagefright/OMXDecoder.h b/include/media/stagefright/OMXDecoder.h new file mode 100644 index 0000000..0859457 --- /dev/null +++ b/include/media/stagefright/OMXDecoder.h @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2009 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. + */ + +#ifndef OMX_DECODER_H_ + +#define OMX_DECODER_H_ + +#include <binder/MemoryDealer.h> +#include <media/stagefright/MediaBuffer.h> +#include <media/stagefright/MediaSource.h> +#include <media/stagefright/OMXClient.h> +#include <utils/KeyedVector.h> +#include <utils/List.h> +#include <utils/threads.h> + +namespace android { + +class OMXMediaBuffer; + +class OMXDecoder : public MediaSource, + public OMXObserver, + public MediaBufferObserver { +public: + static OMXDecoder *Create( + OMXClient *client, const sp<MetaData> &data); + + static OMXDecoder *CreateEncoder( + OMXClient *client, const sp<MetaData> &data); + + virtual ~OMXDecoder(); + + // Caller retains ownership of "source". + void setSource(MediaSource *source); + + virtual status_t start(MetaData *params = NULL); + virtual status_t stop(); + + virtual sp<MetaData> getFormat(); + + virtual status_t read( + MediaBuffer **buffer, const ReadOptions *options = NULL); + + void addCodecSpecificData(const void *data, size_t size); + + // from OMXObserver + virtual void onOMXMessage(const omx_message &msg); + + // from MediaBufferObserver + virtual void signalBufferReturned(MediaBuffer *buffer); + +private: + enum { + kPortIndexInput = 0, + kPortIndexOutput = 1 + }; + + enum PortStatus { + kPortStatusActive = 0, + kPortStatusDisabled = 1, + kPortStatusShutdown = 2, + kPortStatusFlushing = 3 + }; + + OMXClient *mClient; + sp<IOMX> mOMX; + IOMX::node_id mNode; + char *mComponentName; + bool mIsMP3; + + MediaSource *mSource; + sp<MetaData> mOutputFormat; + + Mutex mLock; + Condition mOutputBufferAvailable; + + List<MediaBuffer *> mOutputBuffers; + + struct CodecSpecificData { + void *data; + size_t size; + }; + + List<CodecSpecificData> mCodecSpecificData; + List<CodecSpecificData>::iterator mCodecSpecificDataIterator; + + volatile OMX_STATETYPE mState; + OMX_U32 mPortStatusMask; + bool mShutdownInitiated; + + typedef List<IOMX::buffer_id> BufferList; + Vector<BufferList> mBuffers; + + KeyedVector<IOMX::buffer_id, sp<IMemory> > mBufferMap; + KeyedVector<IOMX::buffer_id, OMXMediaBuffer *> mMediaBufferMap; + + sp<MemoryDealer> mDealer; + + bool mSeeking; + int64_t mSeekTimeUs; + + bool mStarted; + status_t mErrorCondition; + bool mReachedEndOfInput; + + OMXDecoder(OMXClient *client, IOMX::node_id node, + const char *mime, const char *codec); + + void setPortStatus(OMX_U32 port_index, PortStatus status); + PortStatus getPortStatus(OMX_U32 port_index) const; + + void allocateBuffers(OMX_U32 port_index); + + void setAMRFormat(); + void setAACFormat(); + void setVideoOutputFormat(OMX_U32 width, OMX_U32 height); + void setup(); + void dumpPortDefinition(OMX_U32 port_index); + + void onStart(); + void onEvent(OMX_EVENTTYPE event, OMX_U32 data1, OMX_U32 data2); + void onEventCmdComplete(OMX_COMMANDTYPE type, OMX_U32 data); + void onEventPortSettingsChanged(OMX_U32 port_index); + void onStateChanged(OMX_STATETYPE to); + void onEmptyBufferDone(IOMX::buffer_id buffer); + void onFillBufferDone(const omx_message &msg); + + void onRealEmptyBufferDone(IOMX::buffer_id buffer); + void onRealFillBufferDone(const omx_message &msg); + + void initiateShutdown(); + + void freeInputBuffer(IOMX::buffer_id buffer); + void freeOutputBuffer(IOMX::buffer_id buffer); + + void postStart(); + void postEmptyBufferDone(IOMX::buffer_id buffer); + void postInitialFillBuffer(IOMX::buffer_id buffer); + + OMXDecoder(const OMXDecoder &); + OMXDecoder &operator=(const OMXDecoder &); +}; + +} // namespace android + +#endif // OMX_DECODER_H_ diff --git a/include/media/stagefright/QComHardwareRenderer.h b/include/media/stagefright/QComHardwareRenderer.h new file mode 100644 index 0000000..8292dd5 --- /dev/null +++ b/include/media/stagefright/QComHardwareRenderer.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2009 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. + */ + +#ifndef QCOM_HARDWARE_RENDERER_H_ + +#define QCOM_HARDWARE_RENDERER_H_ + +#include <media/stagefright/VideoRenderer.h> +#include <utils/RefBase.h> + +namespace android { + +class ISurface; +class MemoryHeapPmem; + +class QComHardwareRenderer : public VideoRenderer { +public: + QComHardwareRenderer( + const sp<ISurface> &surface, + size_t displayWidth, size_t displayHeight, + size_t decodedWidth, size_t decodedHeight); + + virtual ~QComHardwareRenderer(); + + virtual void render( + const void *data, size_t size, void *platformPrivate); + +private: + sp<ISurface> mISurface; + size_t mDisplayWidth, mDisplayHeight; + size_t mDecodedWidth, mDecodedHeight; + size_t mFrameSize; + sp<MemoryHeapPmem> mMemoryHeap; + + bool getOffset(void *platformPrivate, size_t *offset); + void publishBuffers(uint32_t pmem_fd); + + QComHardwareRenderer(const QComHardwareRenderer &); + QComHardwareRenderer &operator=(const QComHardwareRenderer &); +}; + +} // namespace android + +#endif // QCOM_HARDWARE_RENDERER_H_ diff --git a/include/media/stagefright/SampleTable.h b/include/media/stagefright/SampleTable.h new file mode 100644 index 0000000..712da10 --- /dev/null +++ b/include/media/stagefright/SampleTable.h @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2009 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. + */ + +#ifndef SAMPLE_TABLE_H_ + +#define SAMPLE_TABLE_H_ + +#include <sys/types.h> +#include <stdint.h> + +#include <media/stagefright/MediaErrors.h> +#include <utils/threads.h> + +namespace android { + +class DataSource; + +class SampleTable { +public: + // Caller retains ownership of "source". + SampleTable(DataSource *source); + ~SampleTable(); + + // type can be 'stco' or 'co64'. + status_t setChunkOffsetParams( + uint32_t type, off_t data_offset, off_t data_size); + + status_t setSampleToChunkParams(off_t data_offset, off_t data_size); + + // type can be 'stsz' or 'stz2'. + status_t setSampleSizeParams( + uint32_t type, off_t data_offset, off_t data_size); + + status_t setTimeToSampleParams(off_t data_offset, off_t data_size); + + status_t setSyncSampleParams(off_t data_offset, off_t data_size); + + //////////////////////////////////////////////////////////////////////////// + + uint32_t countChunkOffsets() const; + status_t getChunkOffset(uint32_t chunk_index, off_t *offset); + + status_t getChunkForSample( + uint32_t sample_index, uint32_t *chunk_index, + uint32_t *chunk_relative_sample_index, uint32_t *desc_index); + + uint32_t countSamples() const; + status_t getSampleSize(uint32_t sample_index, size_t *sample_size); + + status_t getSampleOffsetAndSize( + uint32_t sample_index, off_t *offset, size_t *size); + + status_t getMaxSampleSize(size_t *size); + + status_t getDecodingTime(uint32_t sample_index, uint32_t *time); + + enum { + kSyncSample_Flag = 1 + }; + status_t findClosestSample( + uint32_t req_time, uint32_t *sample_index, uint32_t flags); + + status_t findClosestSyncSample( + uint32_t start_sample_index, uint32_t *sample_index); + +private: + DataSource *mDataSource; + Mutex mLock; + + off_t mChunkOffsetOffset; + uint32_t mChunkOffsetType; + uint32_t mNumChunkOffsets; + + off_t mSampleToChunkOffset; + uint32_t mNumSampleToChunkOffsets; + + off_t mSampleSizeOffset; + uint32_t mSampleSizeFieldSize; + uint32_t mDefaultSampleSize; + uint32_t mNumSampleSizes; + + uint32_t mTimeToSampleCount; + uint32_t *mTimeToSample; + + off_t mSyncSampleOffset; + uint32_t mNumSyncSamples; + + SampleTable(const SampleTable &); + SampleTable &operator=(const SampleTable &); +}; + +} // namespace android + +#endif // SAMPLE_TABLE_H_ diff --git a/include/media/stagefright/ShoutcastSource.h b/include/media/stagefright/ShoutcastSource.h new file mode 100644 index 0000000..352857a --- /dev/null +++ b/include/media/stagefright/ShoutcastSource.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2009 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. + */ + +#ifndef SHOUTCAST_SOURCE_H_ + +#define SHOUTCAST_SOURCE_H_ + +#include <sys/types.h> + +#include <media/stagefright/MediaSource.h> + +namespace android { + +class HTTPStream; +class MediaBufferGroup; + +class ShoutcastSource : public MediaSource { +public: + // Assumes ownership of "http". + ShoutcastSource(HTTPStream *http); + virtual ~ShoutcastSource(); + + virtual status_t start(MetaData *params = NULL); + virtual status_t stop(); + + virtual sp<MetaData> getFormat(); + + virtual status_t read( + MediaBuffer **buffer, const ReadOptions *options = NULL); + +private: + HTTPStream *mHttp; + size_t mMetaDataOffset; + size_t mBytesUntilMetaData; + + MediaBufferGroup *mGroup; + bool mStarted; + + ShoutcastSource(const ShoutcastSource &); + ShoutcastSource &operator= (const ShoutcastSource &); +}; + +} // namespace android + +#endif // SHOUTCAST_SOURCE_H_ + diff --git a/include/media/stagefright/SoftwareRenderer.h b/include/media/stagefright/SoftwareRenderer.h new file mode 100644 index 0000000..705b914 --- /dev/null +++ b/include/media/stagefright/SoftwareRenderer.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2009 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. + */ + +#ifndef SOFTWARE_RENDERER_H_ + +#define SOFTWARE_RENDERER_H_ + +#include <media/stagefright/VideoRenderer.h> +#include <utils/RefBase.h> + +namespace android { + +class ISurface; +class MemoryHeapBase; + +class SoftwareRenderer : public VideoRenderer { +public: + SoftwareRenderer( + const sp<ISurface> &surface, + size_t displayWidth, size_t displayHeight, + size_t decodedWidth, size_t decodedHeight); + + virtual ~SoftwareRenderer(); + + virtual void render( + const void *data, size_t size, void *platformPrivate); + +private: + sp<ISurface> mISurface; + size_t mDisplayWidth, mDisplayHeight; + size_t mDecodedWidth, mDecodedHeight; + size_t mFrameSize; + sp<MemoryHeapBase> mMemoryHeap; + int mIndex; + + SoftwareRenderer(const SoftwareRenderer &); + SoftwareRenderer &operator=(const SoftwareRenderer &); +}; + +} // namespace android + +#endif // SOFTWARE_RENDERER_H_ diff --git a/include/media/stagefright/SurfaceRenderer.h b/include/media/stagefright/SurfaceRenderer.h new file mode 100644 index 0000000..298ab50 --- /dev/null +++ b/include/media/stagefright/SurfaceRenderer.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2009 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. + */ + +#ifndef SURFACE_RENDERER_H_ + +#define SURFACE_RENDERER_H_ + +#include <media/stagefright/VideoRenderer.h> +#include <utils/RefBase.h> + +namespace android { + +class Surface; + +class SurfaceRenderer : public VideoRenderer { +public: + SurfaceRenderer( + const sp<Surface> &surface, + size_t displayWidth, size_t displayHeight, + size_t decodedWidth, size_t decodedHeight); + + virtual ~SurfaceRenderer(); + + virtual void render( + const void *data, size_t size, void *platformPrivate); + +private: + sp<Surface> mSurface; + size_t mDisplayWidth, mDisplayHeight; + size_t mDecodedWidth, mDecodedHeight; + + SurfaceRenderer(const SurfaceRenderer &); + SurfaceRenderer &operator=(const SurfaceRenderer &); +}; + +} // namespace android + +#endif // SURFACE_RENDERER_H_ diff --git a/include/media/stagefright/TimeSource.h b/include/media/stagefright/TimeSource.h new file mode 100644 index 0000000..f57d8cf --- /dev/null +++ b/include/media/stagefright/TimeSource.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2009 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. + */ + +#ifndef TIME_SOURCE_H_ + +#define TIME_SOURCE_H_ + +namespace android { + +class TimeSource { +public: + TimeSource() {} + virtual ~TimeSource() {} + + virtual int64_t getRealTimeUs() = 0; + +private: + TimeSource(const TimeSource &); + TimeSource &operator=(const TimeSource &); +}; + +class SystemTimeSource : public TimeSource { +public: + SystemTimeSource(); + + virtual int64_t getRealTimeUs(); + +private: + static int64_t GetSystemTimeUs(); + + int64_t mStartTimeUs; +}; + +} // namespace android + +#endif // TIME_SOURCE_H_ diff --git a/include/media/stagefright/TimedEventQueue.h b/include/media/stagefright/TimedEventQueue.h new file mode 100644 index 0000000..a264421 --- /dev/null +++ b/include/media/stagefright/TimedEventQueue.h @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2009 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. + */ + +#ifndef TIMED_EVENT_QUEUE_H_ + +#define TIMED_EVENT_QUEUE_H_ + +#include <pthread.h> + +#include <utils/List.h> +#include <utils/RefBase.h> +#include <utils/threads.h> + +namespace android { + +struct TimedEventQueue { + + struct Event : public RefBase { + Event() {} + virtual ~Event() {} + + protected: + virtual void fire(TimedEventQueue *queue, int64_t now_us) = 0; + + private: + friend class TimedEventQueue; + + Event(const Event &); + Event &operator=(const Event &); + }; + + TimedEventQueue(); + ~TimedEventQueue(); + + // Start executing the event loop. + void start(); + + // Stop executing the event loop, if flush is false, any pending + // events are discarded, otherwise the queue will stop (and this call + // return) once all pending events have been handled. + void stop(bool flush = false); + + // Posts an event to the front of the queue (after all events that + // have previously been posted to the front but before timed events). + void postEvent(const sp<Event> &event); + + void postEventToBack(const sp<Event> &event); + + // It is an error to post an event with a negative delay. + void postEventWithDelay(const sp<Event> &event, int64_t delay_us); + + // If the event is to be posted at a time that has already passed, + // it will fire as soon as possible. + void postTimedEvent(const sp<Event> &event, int64_t realtime_us); + + // Returns true iff event is currently in the queue and has been + // successfully cancelled. In this case the event will have been + // removed from the queue and won't fire. + bool cancelEvent(const sp<Event> &event); + + static int64_t getRealTimeUs(); + +private: + struct QueueItem { + sp<Event> event; + int64_t realtime_us; + }; + + struct StopEvent : public TimedEventQueue::Event { + virtual void fire(TimedEventQueue *queue, int64_t now_us) { + queue->mStopped = true; + } + }; + + pthread_t mThread; + List<QueueItem> mQueue; + Mutex mLock; + Condition mQueueNotEmptyCondition; + Condition mQueueHeadChangedCondition; + + bool mRunning; + bool mStopped; + + static void *ThreadWrapper(void *me); + void threadEntry(); + + TimedEventQueue(const TimedEventQueue &); + TimedEventQueue &operator=(const TimedEventQueue &); +}; + +} // namespace android + +#endif // TIMED_EVENT_QUEUE_H_ diff --git a/include/media/stagefright/Utils.h b/include/media/stagefright/Utils.h new file mode 100644 index 0000000..30c7f11 --- /dev/null +++ b/include/media/stagefright/Utils.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2009 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. + */ + +#ifndef UTILS_H_ + +#define UTILS_H_ + +#include <stdint.h> + +namespace android { + +#define FOURCC(c1, c2, c3, c4) \ + (c1 << 24 | c2 << 16 | c3 << 8 | c4) + +uint16_t U16_AT(const uint8_t *ptr); +uint32_t U32_AT(const uint8_t *ptr); +uint64_t U64_AT(const uint8_t *ptr); + +uint64_t ntoh64(uint64_t x); +uint64_t hton64(uint64_t x); + +} // namespace android + +#endif // UTILS_H_ diff --git a/include/media/stagefright/VideoRenderer.h b/include/media/stagefright/VideoRenderer.h new file mode 100644 index 0000000..f80b277 --- /dev/null +++ b/include/media/stagefright/VideoRenderer.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2009 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. + */ + +#ifndef VIDEO_RENDERER_H_ + +#define VIDEO_RENDERER_H_ + +#include <sys/types.h> + +namespace android { + +class VideoRenderer { +public: + virtual ~VideoRenderer() {} + + virtual void render( + const void *data, size_t size, void *platformPrivate) = 0; + +protected: + VideoRenderer() {} + + VideoRenderer(const VideoRenderer &); + VideoRenderer &operator=(const VideoRenderer &); +}; + +} // namespace android + +#endif // VIDEO_RENDERER_H_ diff --git a/include/media/stagefright/string.h b/include/media/stagefright/string.h new file mode 100644 index 0000000..5dc7116 --- /dev/null +++ b/include/media/stagefright/string.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2009 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. + */ + +#ifndef STRING_H_ + +#define STRING_H_ + +#include <utils/String8.h> + +namespace android { + +class string { +public: + typedef size_t size_type; + static size_type npos; + + string(); + string(const char *s); + string(const char *s, size_t length); + string(const string &from, size_type start, size_type length = npos); + + const char *c_str() const; + size_type size() const; + + void clear(); + void erase(size_type from, size_type length); + + size_type find(char c) const; + + bool operator<(const string &other) const; + bool operator==(const string &other) const; + + string &operator+=(char c); + +private: + String8 mString; +}; + +} // namespace android + +#endif // STRING_H_ diff --git a/media/libmedia/Android.mk b/media/libmedia/Android.mk index ebbf13f..cdaab04 100644 --- a/media/libmedia/Android.mk +++ b/media/libmedia/Android.mk @@ -18,7 +18,8 @@ LOCAL_SRC_FILES:= \ IMediaMetadataRetriever.cpp \ mediametadataretriever.cpp \ ToneGenerator.cpp \ - JetPlayer.cpp + JetPlayer.cpp \ + IOMX.cpp LOCAL_SHARED_LIBRARIES := \ libui libcutils libutils libbinder libsonivox @@ -34,6 +35,7 @@ LOCAL_SHARED_LIBRARIES += libdl endif LOCAL_C_INCLUDES := \ - $(call include-path-for, graphics corecg) + $(call include-path-for, graphics corecg) \ + $(TOP)/external/opencore/extern_libs_v2/khronos/openmax/include include $(BUILD_SHARED_LIBRARY) diff --git a/media/libmedia/IMediaPlayerService.cpp b/media/libmedia/IMediaPlayerService.cpp index 0f64259..8d2c360 100644 --- a/media/libmedia/IMediaPlayerService.cpp +++ b/media/libmedia/IMediaPlayerService.cpp @@ -17,12 +17,14 @@ #include <stdint.h> #include <sys/types.h> -#include <binder/Parcel.h> +#include <binder/Parcel.h> #include <binder/IMemory.h> -#include <utils/Errors.h> // for status_t #include <media/IMediaPlayerService.h> #include <media/IMediaRecorder.h> +#include <media/IOMX.h> + +#include <utils/Errors.h> // for status_t namespace android { @@ -33,6 +35,7 @@ enum { DECODE_FD, CREATE_MEDIA_RECORDER, CREATE_METADATA_RETRIEVER, + CREATE_OMX, }; class BpMediaPlayerService: public BpInterface<IMediaPlayerService> @@ -110,6 +113,13 @@ public: *pFormat = reply.readInt32(); return interface_cast<IMemory>(reply.readStrongBinder()); } + + virtual sp<IOMX> createOMX() { + Parcel data, reply; + data.writeInterfaceToken(IMediaPlayerService::getInterfaceDescriptor()); + remote()->transact(CREATE_OMX, data, &reply); + return interface_cast<IOMX>(reply.readStrongBinder()); + } }; IMPLEMENT_META_INTERFACE(MediaPlayerService, "android.media.IMediaPlayerService"); @@ -182,6 +192,12 @@ status_t BnMediaPlayerService::onTransact( reply->writeStrongBinder(retriever->asBinder()); return NO_ERROR; } break; + case CREATE_OMX: { + CHECK_INTERFACE(IMediaPlayerService, data, reply); + sp<IOMX> omx = createOMX(); + reply->writeStrongBinder(omx->asBinder()); + return NO_ERROR; + } break; default: return BBinder::onTransact(code, data, reply, flags); } diff --git a/media/libmedia/IOMX.cpp b/media/libmedia/IOMX.cpp new file mode 100644 index 0000000..f2a657a --- /dev/null +++ b/media/libmedia/IOMX.cpp @@ -0,0 +1,561 @@ +//#define LOG_NDEBUG 0 +#define LOG_TAG "IOMX" +#include <utils/Log.h> + +#include <binder/IMemory.h> +#include <binder/Parcel.h> +#include <media/IOMX.h> + +namespace android { + +enum { + CONNECT = IBinder::FIRST_CALL_TRANSACTION, + LIST_NODES, + ALLOCATE_NODE, + FREE_NODE, + SEND_COMMAND, + GET_PARAMETER, + SET_PARAMETER, + USE_BUFFER, + ALLOC_BUFFER, + ALLOC_BUFFER_WITH_BACKUP, + FREE_BUFFER, + OBSERVE_NODE, + FILL_BUFFER, + EMPTY_BUFFER, + OBSERVER_ON_MSG, +}; + +static void *readVoidStar(const Parcel *parcel) { + // FIX if sizeof(void *) != sizeof(int32) + return (void *)parcel->readInt32(); +} + +static void writeVoidStar(void *x, Parcel *parcel) { + // FIX if sizeof(void *) != sizeof(int32) + parcel->writeInt32((int32_t)x); +} + +class BpOMX : public BpInterface<IOMX> { +public: + BpOMX(const sp<IBinder> &impl) + : BpInterface<IOMX>(impl) { + } + +#if IOMX_USES_SOCKETS + virtual status_t connect(int *sd) { + Parcel data, reply; + data.writeInterfaceToken(IOMX::getInterfaceDescriptor()); + remote()->transact(CONNECT, data, &reply); + + status_t err = reply.readInt32(); + if (err == OK) { + *sd = dup(reply.readFileDescriptor()); + } else { + *sd = -1; + } + + return reply.readInt32(); + } +#endif + + virtual status_t list_nodes(List<String8> *list) { + list->clear(); + + Parcel data, reply; + data.writeInterfaceToken(IOMX::getInterfaceDescriptor()); + remote()->transact(LIST_NODES, data, &reply); + + int32_t n = reply.readInt32(); + for (int32_t i = 0; i < n; ++i) { + String8 s = reply.readString8(); + + list->push_back(s); + } + + return OK; + } + + virtual status_t allocate_node(const char *name, node_id *node) { + Parcel data, reply; + data.writeInterfaceToken(IOMX::getInterfaceDescriptor()); + data.writeCString(name); + remote()->transact(ALLOCATE_NODE, data, &reply); + + status_t err = reply.readInt32(); + if (err == OK) { + *node = readVoidStar(&reply); + } else { + *node = 0; + } + + return err; + } + + virtual status_t free_node(node_id node) { + Parcel data, reply; + data.writeInterfaceToken(IOMX::getInterfaceDescriptor()); + writeVoidStar(node, &data); + remote()->transact(FREE_NODE, data, &reply); + + return reply.readInt32(); + } + + virtual status_t send_command( + node_id node, OMX_COMMANDTYPE cmd, OMX_S32 param) { + Parcel data, reply; + data.writeInterfaceToken(IOMX::getInterfaceDescriptor()); + writeVoidStar(node, &data); + data.writeInt32(cmd); + data.writeInt32(param); + remote()->transact(SEND_COMMAND, data, &reply); + + return reply.readInt32(); + } + + virtual status_t get_parameter( + node_id node, OMX_INDEXTYPE index, + void *params, size_t size) { + Parcel data, reply; + data.writeInterfaceToken(IOMX::getInterfaceDescriptor()); + writeVoidStar(node, &data); + data.writeInt32(index); + data.writeInt32(size); + data.write(params, size); + remote()->transact(GET_PARAMETER, data, &reply); + + status_t err = reply.readInt32(); + if (err != OK) { + return err; + } + + reply.read(params, size); + + return OK; + } + + virtual status_t set_parameter( + node_id node, OMX_INDEXTYPE index, + const void *params, size_t size) { + Parcel data, reply; + data.writeInterfaceToken(IOMX::getInterfaceDescriptor()); + writeVoidStar(node, &data); + data.writeInt32(index); + data.writeInt32(size); + data.write(params, size); + remote()->transact(SET_PARAMETER, data, &reply); + + return reply.readInt32(); + } + + virtual status_t use_buffer( + node_id node, OMX_U32 port_index, const sp<IMemory> ¶ms, + buffer_id *buffer) { + Parcel data, reply; + data.writeInterfaceToken(IOMX::getInterfaceDescriptor()); + writeVoidStar(node, &data); + data.writeInt32(port_index); + data.writeStrongBinder(params->asBinder()); + remote()->transact(USE_BUFFER, data, &reply); + + status_t err = reply.readInt32(); + if (err != OK) { + *buffer = 0; + + return err; + } + + *buffer = readVoidStar(&reply); + + return err; + } + + virtual status_t allocate_buffer( + node_id node, OMX_U32 port_index, size_t size, + buffer_id *buffer) { + Parcel data, reply; + data.writeInterfaceToken(IOMX::getInterfaceDescriptor()); + writeVoidStar(node, &data); + data.writeInt32(port_index); + data.writeInt32(size); + remote()->transact(ALLOC_BUFFER, data, &reply); + + status_t err = reply.readInt32(); + if (err != OK) { + *buffer = 0; + + return err; + } + + *buffer = readVoidStar(&reply); + + return err; + } + + virtual status_t allocate_buffer_with_backup( + node_id node, OMX_U32 port_index, const sp<IMemory> ¶ms, + buffer_id *buffer) { + Parcel data, reply; + data.writeInterfaceToken(IOMX::getInterfaceDescriptor()); + writeVoidStar(node, &data); + data.writeInt32(port_index); + data.writeStrongBinder(params->asBinder()); + remote()->transact(ALLOC_BUFFER_WITH_BACKUP, data, &reply); + + status_t err = reply.readInt32(); + if (err != OK) { + *buffer = 0; + + return err; + } + + *buffer = readVoidStar(&reply); + + return err; + } + + virtual status_t free_buffer( + node_id node, OMX_U32 port_index, buffer_id buffer) { + Parcel data, reply; + data.writeInterfaceToken(IOMX::getInterfaceDescriptor()); + writeVoidStar(node, &data); + data.writeInt32(port_index); + writeVoidStar(buffer, &data); + remote()->transact(FREE_BUFFER, data, &reply); + + return reply.readInt32(); + } + +#if !IOMX_USES_SOCKETS + virtual status_t observe_node( + node_id node, const sp<IOMXObserver> &observer) { + Parcel data, reply; + data.writeInterfaceToken(IOMX::getInterfaceDescriptor()); + writeVoidStar(node, &data); + data.writeStrongBinder(observer->asBinder()); + remote()->transact(OBSERVE_NODE, data, &reply); + + return reply.readInt32(); + } + + virtual void fill_buffer(node_id node, buffer_id buffer) { + Parcel data, reply; + data.writeInterfaceToken(IOMX::getInterfaceDescriptor()); + writeVoidStar(node, &data); + writeVoidStar(buffer, &data); + remote()->transact(FILL_BUFFER, data, &reply, IBinder::FLAG_ONEWAY); + } + + virtual void empty_buffer( + node_id node, + buffer_id buffer, + OMX_U32 range_offset, OMX_U32 range_length, + OMX_U32 flags, OMX_TICKS timestamp) { + Parcel data, reply; + data.writeInterfaceToken(IOMX::getInterfaceDescriptor()); + writeVoidStar(node, &data); + writeVoidStar(buffer, &data); + data.writeInt32(range_offset); + data.writeInt32(range_length); + data.writeInt32(flags); + data.writeInt64(timestamp); + remote()->transact(EMPTY_BUFFER, data, &reply, IBinder::FLAG_ONEWAY); + } +#endif +}; + +IMPLEMENT_META_INTERFACE(OMX, "android.hardware.IOMX"); + +//////////////////////////////////////////////////////////////////////////////// + +#define CHECK_INTERFACE(interface, data, reply) \ + do { if (!data.enforceInterface(interface::getInterfaceDescriptor())) { \ + LOGW("Call incorrectly routed to " #interface); \ + return PERMISSION_DENIED; \ + } } while (0) + +status_t BnOMX::onTransact( + uint32_t code, const Parcel &data, Parcel *reply, uint32_t flags) { + switch (code) { +#if IOMX_USES_SOCKETS + case CONNECT: + { + CHECK_INTERFACE(IOMX, data, reply); + + int s; + status_t err = connect(&s); + + reply->writeInt32(err); + if (err == OK) { + assert(s >= 0); + reply->writeDupFileDescriptor(s); + close(s); + s = -1; + } else { + assert(s == -1); + } + + return NO_ERROR; + } +#endif + + case LIST_NODES: + { + CHECK_INTERFACE(IOMX, data, reply); + + List<String8> list; + list_nodes(&list); + + reply->writeInt32(list.size()); + for (List<String8>::iterator it = list.begin(); + it != list.end(); ++it) { + reply->writeString8(*it); + } + + return NO_ERROR; + } + + case ALLOCATE_NODE: + { + CHECK_INTERFACE(IOMX, data, reply); + + node_id node; + status_t err = allocate_node(data.readCString(), &node); + reply->writeInt32(err); + if (err == OK) { + writeVoidStar(node, reply); + } + + return NO_ERROR; + } + + case FREE_NODE: + { + CHECK_INTERFACE(IOMX, data, reply); + + node_id node = readVoidStar(&data); + + reply->writeInt32(free_node(node)); + + return NO_ERROR; + } + + case SEND_COMMAND: + { + CHECK_INTERFACE(IOMX, data, reply); + + node_id node = readVoidStar(&data); + + OMX_COMMANDTYPE cmd = + static_cast<OMX_COMMANDTYPE>(data.readInt32()); + + OMX_S32 param = data.readInt32(); + reply->writeInt32(send_command(node, cmd, param)); + + return NO_ERROR; + } + + case GET_PARAMETER: + { + CHECK_INTERFACE(IOMX, data, reply); + + node_id node = readVoidStar(&data); + OMX_INDEXTYPE index = static_cast<OMX_INDEXTYPE>(data.readInt32()); + + size_t size = data.readInt32(); + + // XXX I am not happy with this but Parcel::readInplace didn't work. + void *params = malloc(size); + data.read(params, size); + + status_t err = get_parameter(node, index, params, size); + + reply->writeInt32(err); + + if (err == OK) { + reply->write(params, size); + } + + free(params); + params = NULL; + + return NO_ERROR; + } + + case SET_PARAMETER: + { + CHECK_INTERFACE(IOMX, data, reply); + + node_id node = readVoidStar(&data); + OMX_INDEXTYPE index = static_cast<OMX_INDEXTYPE>(data.readInt32()); + + size_t size = data.readInt32(); + void *params = const_cast<void *>(data.readInplace(size)); + + reply->writeInt32(set_parameter(node, index, params, size)); + + return NO_ERROR; + } + + case USE_BUFFER: + { + CHECK_INTERFACE(IOMX, data, reply); + + node_id node = readVoidStar(&data); + OMX_U32 port_index = data.readInt32(); + sp<IMemory> params = + interface_cast<IMemory>(data.readStrongBinder()); + + buffer_id buffer; + status_t err = use_buffer(node, port_index, params, &buffer); + reply->writeInt32(err); + + if (err == OK) { + writeVoidStar(buffer, reply); + } + + return NO_ERROR; + } + + case ALLOC_BUFFER: + { + CHECK_INTERFACE(IOMX, data, reply); + + node_id node = readVoidStar(&data); + OMX_U32 port_index = data.readInt32(); + size_t size = data.readInt32(); + + buffer_id buffer; + status_t err = allocate_buffer(node, port_index, size, &buffer); + reply->writeInt32(err); + + if (err == OK) { + writeVoidStar(buffer, reply); + } + + return NO_ERROR; + } + + case ALLOC_BUFFER_WITH_BACKUP: + { + CHECK_INTERFACE(IOMX, data, reply); + + node_id node = readVoidStar(&data); + OMX_U32 port_index = data.readInt32(); + sp<IMemory> params = + interface_cast<IMemory>(data.readStrongBinder()); + + buffer_id buffer; + status_t err = allocate_buffer_with_backup( + node, port_index, params, &buffer); + + reply->writeInt32(err); + + if (err == OK) { + writeVoidStar(buffer, reply); + } + + return NO_ERROR; + } + + case FREE_BUFFER: + { + CHECK_INTERFACE(IOMX, data, reply); + + node_id node = readVoidStar(&data); + OMX_U32 port_index = data.readInt32(); + buffer_id buffer = readVoidStar(&data); + reply->writeInt32(free_buffer(node, port_index, buffer)); + + return NO_ERROR; + } + +#if !IOMX_USES_SOCKETS + case OBSERVE_NODE: + { + CHECK_INTERFACE(IOMX, data, reply); + + node_id node = readVoidStar(&data); + sp<IOMXObserver> observer = + interface_cast<IOMXObserver>(data.readStrongBinder()); + reply->writeInt32(observe_node(node, observer)); + + return NO_ERROR; + } + + case FILL_BUFFER: + { + CHECK_INTERFACE(IOMX, data, reply); + + node_id node = readVoidStar(&data); + buffer_id buffer = readVoidStar(&data); + fill_buffer(node, buffer); + + return NO_ERROR; + } + + case EMPTY_BUFFER: + { + CHECK_INTERFACE(IOMX, data, reply); + + node_id node = readVoidStar(&data); + buffer_id buffer = readVoidStar(&data); + OMX_U32 range_offset = data.readInt32(); + OMX_U32 range_length = data.readInt32(); + OMX_U32 flags = data.readInt32(); + OMX_TICKS timestamp = data.readInt64(); + + empty_buffer( + node, buffer, range_offset, range_length, + flags, timestamp); + + return NO_ERROR; + } +#endif + + default: + return BBinder::onTransact(code, data, reply, flags); + } +} + +//////////////////////////////////////////////////////////////////////////////// + +class BpOMXObserver : public BpInterface<IOMXObserver> { +public: + BpOMXObserver(const sp<IBinder> &impl) + : BpInterface<IOMXObserver>(impl) { + } + + virtual void on_message(const omx_message &msg) { + Parcel data, reply; + data.writeInterfaceToken(IOMXObserver::getInterfaceDescriptor()); + data.write(&msg, sizeof(msg)); + + remote()->transact(OBSERVER_ON_MSG, data, &reply, IBinder::FLAG_ONEWAY); + } +}; + +IMPLEMENT_META_INTERFACE(OMXObserver, "android.hardware.IOMXObserver"); + +status_t BnOMXObserver::onTransact( + uint32_t code, const Parcel &data, Parcel *reply, uint32_t flags) { + switch (code) { + case OBSERVER_ON_MSG: + { + CHECK_INTERFACE(IOMXObserver, data, reply); + + omx_message msg; + data.read(&msg, sizeof(msg)); + + // XXX Could use readInplace maybe? + on_message(msg); + + return NO_ERROR; + } + + default: + return BBinder::onTransact(code, data, reply, flags); + } +} + +} // namespace android diff --git a/media/libmediaplayerservice/Android.mk b/media/libmediaplayerservice/Android.mk index 0877142..6978b3d 100644 --- a/media/libmediaplayerservice/Android.mk +++ b/media/libmediaplayerservice/Android.mk @@ -29,9 +29,22 @@ LOCAL_SHARED_LIBRARIES := \ libandroid_runtime LOCAL_C_INCLUDES := external/tremor/Tremor \ - $(call include-path-for, graphics corecg) + $(call include-path-for, graphics corecg) \ + $(TOP)/external/opencore/extern_libs_v2/khronos/openmax/include LOCAL_MODULE:= libmediaplayerservice +ifeq ($(BUILD_WITH_STAGEFRIGHT),true) + LOCAL_SRC_FILES += StagefrightPlayer.cpp + + LOCAL_SHARED_LIBRARIES += \ + libstagefright \ + libstagefright_omx + + LOCAL_C_INCLUDES += $(TOP)/frameworks/base/media/libstagefright/omx + + LOCAL_CFLAGS += -DBUILD_WITH_STAGEFRIGHT -DUSE_STAGEFRIGHT +endif + include $(BUILD_SHARED_LIBRARY) diff --git a/media/libmediaplayerservice/MediaPlayerService.cpp b/media/libmediaplayerservice/MediaPlayerService.cpp index 493dc13..c575f6c 100644 --- a/media/libmediaplayerservice/MediaPlayerService.cpp +++ b/media/libmediaplayerservice/MediaPlayerService.cpp @@ -58,6 +58,15 @@ #include "MidiFile.h" #include "VorbisPlayer.h" #include <media/PVPlayer.h> +#if USE_STAGEFRIGHT +#include "StagefrightPlayer.h" +#endif + +#ifdef BUILD_WITH_STAGEFRIGHT +#include <OMX.h> +#else +#include <media/IOMX.h> +#endif /* desktop Linux needs a little help with gettid() */ #if defined(HAVE_GETTID) && !defined(HAVE_ANDROID_OS) @@ -186,6 +195,10 @@ typedef struct { const player_type playertype; } extmap; extmap FILE_EXTS [] = { +#if USE_STAGEFRIGHT + {".mp4", STAGEFRIGHT_PLAYER}, + {".3gp", STAGEFRIGHT_PLAYER}, +#endif {".mid", SONIVOX_PLAYER}, {".midi", SONIVOX_PLAYER}, {".smf", SONIVOX_PLAYER}, @@ -271,6 +284,14 @@ sp<IMediaPlayer> MediaPlayerService::create(pid_t pid, const sp<IMediaPlayerClie return c; } +sp<IOMX> MediaPlayerService::createOMX() { +#ifdef BUILD_WITH_STAGEFRIGHT + return new OMX; +#else + return NULL; +#endif +} + status_t MediaPlayerService::AudioCache::dump(int fd, const Vector<String16>& args) const { const size_t SIZE = 256; @@ -577,6 +598,7 @@ void MediaPlayerService::Client::disconnect() p = mPlayer; } mClient.clear(); + mPlayer.clear(); // clear the notification to prevent callbacks to dead client @@ -624,13 +646,16 @@ static player_type getPlayerType(int fd, int64_t offset, int64_t length) EAS_Shutdown(easdata); } +#if USE_STAGEFRIGHT + return STAGEFRIGHT_PLAYER; +#endif + // Fall through to PV return PV_PLAYER; } static player_type getPlayerType(const char* url) { - // use MidiFile for MIDI extensions int lenURL = strlen(url); for (int i = 0; i < NELEM(FILE_EXTS); ++i) { @@ -643,6 +668,10 @@ static player_type getPlayerType(const char* url) } } +#if USE_STAGEFRIGHT + return STAGEFRIGHT_PLAYER; +#endif + // Fall through to PV return PV_PLAYER; } @@ -666,6 +695,17 @@ static sp<MediaPlayerBase> createPlayer(player_type playerType, void* cookie, LOGV(" create VorbisPlayer"); p = new VorbisPlayer(); break; +#if USE_STAGEFRIGHT + case STAGEFRIGHT_PLAYER: + LOGV(" create StagefrightPlayer"); + p = new StagefrightPlayer; + break; +#else + case STAGEFRIGHT_PLAYER: + LOG_ALWAYS_FATAL( + "Should not be here, stagefright player not enabled."); + break; +#endif } if (p != NULL) { if (p->initCheck() == NO_ERROR) { @@ -1136,7 +1176,8 @@ Exit: #undef LOG_TAG #define LOG_TAG "AudioSink" MediaPlayerService::AudioOutput::AudioOutput() -{ + : mCallback(NULL), + mCallbackCookie(NULL) { mTrack = 0; mStreamType = AudioSystem::MUSIC; mLeftVolume = 1.0; @@ -1206,8 +1247,13 @@ float MediaPlayerService::AudioOutput::msecsPerFrame() const return mMsecsPerFrame; } -status_t MediaPlayerService::AudioOutput::open(uint32_t sampleRate, int channelCount, int format, int bufferCount) +status_t MediaPlayerService::AudioOutput::open( + uint32_t sampleRate, int channelCount, int format, int bufferCount, + AudioCallback cb, void *cookie) { + mCallback = cb; + mCallbackCookie = cookie; + // Check argument "bufferCount" against the mininum buffer count if (bufferCount < mMinBufferCount) { LOGD("bufferCount (%d) is too small and increased to %d", bufferCount, mMinBufferCount); @@ -1228,7 +1274,17 @@ status_t MediaPlayerService::AudioOutput::open(uint32_t sampleRate, int channelC } frameCount = (sampleRate*afFrameCount*bufferCount)/afSampleRate; - AudioTrack *t = new AudioTrack(mStreamType, sampleRate, format, channelCount, frameCount); + + AudioTrack *t; + if (mCallback != NULL) { + t = new AudioTrack( + mStreamType, sampleRate, format, channelCount, frameCount, + 0 /* flags */, CallbackWrapper, this); + } else { + t = new AudioTrack( + mStreamType, sampleRate, format, channelCount, frameCount); + } + if ((t == 0) || (t->initCheck() != NO_ERROR)) { LOGE("Unable to create audio track"); delete t; @@ -1254,6 +1310,8 @@ void MediaPlayerService::AudioOutput::start() ssize_t MediaPlayerService::AudioOutput::write(const void* buffer, size_t size) { + LOG_FATAL_IF(mCallback != NULL, "Don't call write if supplying a callback."); + //LOGV("write(%p, %u)", buffer, size); if (mTrack) return mTrack->write(buffer, size); return NO_INIT; @@ -1294,6 +1352,20 @@ void MediaPlayerService::AudioOutput::setVolume(float left, float right) } } +// static +void MediaPlayerService::AudioOutput::CallbackWrapper( + int event, void *cookie, void *info) { + if (event != AudioTrack::EVENT_MORE_DATA) { + return; + } + + AudioOutput *me = (AudioOutput *)cookie; + AudioTrack::Buffer *buffer = (AudioTrack::Buffer *)info; + + (*me->mCallback)( + me, buffer->raw, buffer->size, me->mCallbackCookie); +} + #undef LOG_TAG #define LOG_TAG "AudioCache" MediaPlayerService::AudioCache::AudioCache(const char* name) : @@ -1314,8 +1386,14 @@ float MediaPlayerService::AudioCache::msecsPerFrame() const return mMsecsPerFrame; } -status_t MediaPlayerService::AudioCache::open(uint32_t sampleRate, int channelCount, int format, int bufferCount) +status_t MediaPlayerService::AudioCache::open( + uint32_t sampleRate, int channelCount, int format, int bufferCount, + AudioCallback cb, void *cookie) { + if (cb != NULL) { + return UNKNOWN_ERROR; // TODO: implement this. + } + LOGV("open(%u, %d, %d, %d)", sampleRate, channelCount, format, bufferCount); if (mHeap->getHeapID() < 0) return NO_INIT; mSampleRate = sampleRate; diff --git a/media/libmediaplayerservice/MediaPlayerService.h b/media/libmediaplayerservice/MediaPlayerService.h index db3d5d7..94cb917 100644 --- a/media/libmediaplayerservice/MediaPlayerService.h +++ b/media/libmediaplayerservice/MediaPlayerService.h @@ -35,6 +35,7 @@ typedef int32_t MetadataType; class IMediaRecorder; class IMediaMetadataRetriever; +class IOMX; #define CALLBACK_ANTAGONIZER 0 #if CALLBACK_ANTAGONIZER @@ -75,7 +76,12 @@ class MediaPlayerService : public BnMediaPlayerService virtual ssize_t frameSize() const; virtual uint32_t latency() const; virtual float msecsPerFrame() const; - virtual status_t open(uint32_t sampleRate, int channelCount, int format, int bufferCount=4); + + virtual status_t open( + uint32_t sampleRate, int channelCount, + int format, int bufferCount, + AudioCallback cb, void *cookie); + virtual void start(); virtual ssize_t write(const void* buffer, size_t size); virtual void stop(); @@ -90,8 +96,12 @@ class MediaPlayerService : public BnMediaPlayerService static int getMinBufferCount(); private: static void setMinBufferCount(); + static void CallbackWrapper( + int event, void *me, void *info); AudioTrack* mTrack; + AudioCallback mCallback; + void * mCallbackCookie; int mStreamType; float mLeftVolume; float mRightVolume; @@ -119,7 +129,12 @@ class MediaPlayerService : public BnMediaPlayerService virtual ssize_t frameSize() const { return ssize_t(mChannelCount * ((mFormat == AudioSystem::PCM_16_BIT)?sizeof(int16_t):sizeof(u_int8_t))); } virtual uint32_t latency() const; virtual float msecsPerFrame() const; - virtual status_t open(uint32_t sampleRate, int channelCount, int format, int bufferCount=1); + + virtual status_t open( + uint32_t sampleRate, int channelCount, int format, + int bufferCount = 1, + AudioCallback cb = NULL, void *cookie = NULL); + virtual void start() {} virtual ssize_t write(const void* buffer, size_t size); virtual void stop() {} @@ -166,6 +181,7 @@ public: virtual sp<IMediaPlayer> create(pid_t pid, const sp<IMediaPlayerClient>& client, int fd, int64_t offset, int64_t length); virtual sp<IMemory> decode(const char* url, uint32_t *pSampleRate, int* pNumChannels, int* pFormat); virtual sp<IMemory> decode(int fd, int64_t offset, int64_t length, uint32_t *pSampleRate, int* pNumChannels, int* pFormat); + virtual sp<IOMX> createOMX(); virtual status_t dump(int fd, const Vector<String16>& args); diff --git a/media/libmediaplayerservice/StagefrightPlayer.cpp b/media/libmediaplayerservice/StagefrightPlayer.cpp new file mode 100644 index 0000000..ad1afbb --- /dev/null +++ b/media/libmediaplayerservice/StagefrightPlayer.cpp @@ -0,0 +1,208 @@ +//#define LOG_NDEBUG 0 +#define LOG_TAG "StagefrightPlayer" +#include <utils/Log.h> + +#include "StagefrightPlayer.h" +#include <media/stagefright/MediaPlayerImpl.h> + +namespace android { + +StagefrightPlayer::StagefrightPlayer() + : mPlayer(NULL) { + LOGV("StagefrightPlayer"); +} + +StagefrightPlayer::~StagefrightPlayer() { + LOGV("~StagefrightPlayer"); + reset(); + LOGV("~StagefrightPlayer done."); +} + +status_t StagefrightPlayer::initCheck() { + LOGV("initCheck"); + return OK; +} + +status_t StagefrightPlayer::setDataSource(const char *url) { + LOGV("setDataSource('%s')", url); + + reset(); + mPlayer = new MediaPlayerImpl(url); + + status_t err = mPlayer->initCheck(); + if (err != OK) { + delete mPlayer; + mPlayer = NULL; + } else { + mPlayer->setAudioSink(mAudioSink); + } + + return err; +} + +status_t StagefrightPlayer::setDataSource(int fd, int64_t offset, int64_t length) { + LOGV("setDataSource(%d, %lld, %lld)", fd, offset, length); + + reset(); + mPlayer = new MediaPlayerImpl(fd, offset, length); + + status_t err = mPlayer->initCheck(); + if (err != OK) { + delete mPlayer; + mPlayer = NULL; + } else { + mPlayer->setAudioSink(mAudioSink); + } + + return err; +} + +status_t StagefrightPlayer::setVideoSurface(const sp<ISurface> &surface) { + LOGV("setVideoSurface"); + + if (mPlayer == NULL) { + return NO_INIT; + } + + mPlayer->setISurface(surface); + + return OK; +} + +status_t StagefrightPlayer::prepare() { + LOGV("prepare"); + + if (mPlayer == NULL) { + return NO_INIT; + } + + sendEvent( + MEDIA_SET_VIDEO_SIZE, + mPlayer->getWidth(), mPlayer->getHeight()); + + return OK; +} + +status_t StagefrightPlayer::prepareAsync() { + LOGV("prepareAsync"); + + status_t err = prepare(); + + if (err != OK) { + return err; + } + + sendEvent(MEDIA_PREPARED); + + return OK; +} + +status_t StagefrightPlayer::start() { + LOGV("start"); + + if (mPlayer == NULL) { + return NO_INIT; + } + + mPlayer->play(); + + return OK; +} + +status_t StagefrightPlayer::stop() { + LOGV("stop"); + + if (mPlayer == NULL) { + return NO_INIT; + } + + reset(); + + return OK; +} + +status_t StagefrightPlayer::pause() { + LOGV("pause"); + + if (mPlayer == NULL) { + return NO_INIT; + } + + mPlayer->pause(); + + return OK; +} + +bool StagefrightPlayer::isPlaying() { + LOGV("isPlaying"); + return mPlayer != NULL && mPlayer->isPlaying(); +} + +status_t StagefrightPlayer::seekTo(int msec) { + LOGV("seekTo"); + + if (mPlayer == NULL) { + return NO_INIT; + } + + status_t err = mPlayer->seekTo((int64_t)msec * 1000); + + sendEvent(MEDIA_SEEK_COMPLETE); + + return err; +} + +status_t StagefrightPlayer::getCurrentPosition(int *msec) { + LOGV("getCurrentPosition"); + + if (mPlayer == NULL) { + return NO_INIT; + } + + *msec = mPlayer->getPosition() / 1000; + return OK; +} + +status_t StagefrightPlayer::getDuration(int *msec) { + LOGV("getDuration"); + + if (mPlayer == NULL) { + return NO_INIT; + } + + *msec = mPlayer->getDuration() / 1000; + return OK; +} + +status_t StagefrightPlayer::reset() { + LOGV("reset"); + + delete mPlayer; + mPlayer = NULL; + + return OK; +} + +status_t StagefrightPlayer::setLooping(int loop) { + LOGV("setLooping"); + return UNKNOWN_ERROR; +} + +player_type StagefrightPlayer::playerType() { + LOGV("playerType"); + return STAGEFRIGHT_PLAYER; +} + +status_t StagefrightPlayer::invoke(const Parcel &request, Parcel *reply) { + return INVALID_OPERATION; +} + +void StagefrightPlayer::setAudioSink(const sp<AudioSink> &audioSink) { + MediaPlayerInterface::setAudioSink(audioSink); + + if (mPlayer != NULL) { + mPlayer->setAudioSink(audioSink); + } +} + +} // namespace android diff --git a/media/libmediaplayerservice/StagefrightPlayer.h b/media/libmediaplayerservice/StagefrightPlayer.h new file mode 100644 index 0000000..f214872 --- /dev/null +++ b/media/libmediaplayerservice/StagefrightPlayer.h @@ -0,0 +1,60 @@ +/* +** +** Copyright 2009, 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. +*/ + +#ifndef ANDROID_STAGEFRIGHTPLAYER_H +#define ANDROID_STAGEFRIGHTPLAYER_H + +#include <media/MediaPlayerInterface.h> + +namespace android { + +class MediaPlayerImpl; + +class StagefrightPlayer : public MediaPlayerInterface { +public: + StagefrightPlayer(); + virtual ~StagefrightPlayer(); + + virtual status_t initCheck(); + virtual status_t setDataSource(const char *url); + virtual status_t setDataSource(int fd, int64_t offset, int64_t length); + virtual status_t setVideoSurface(const sp<ISurface> &surface); + virtual status_t prepare(); + virtual status_t prepareAsync(); + virtual status_t start(); + virtual status_t stop(); + virtual status_t pause(); + virtual bool isPlaying(); + virtual status_t seekTo(int msec); + virtual status_t getCurrentPosition(int *msec); + virtual status_t getDuration(int *msec); + virtual status_t reset(); + virtual status_t setLooping(int loop); + virtual player_type playerType(); + virtual status_t invoke(const Parcel &request, Parcel *reply); + virtual void setAudioSink(const sp<AudioSink> &audioSink); + +private: + MediaPlayerImpl *mPlayer; + + StagefrightPlayer(const StagefrightPlayer &); + StagefrightPlayer &operator=(const StagefrightPlayer &); +}; + +} // namespace android + +#endif // ANDROID_STAGEFRIGHTPLAYER_H diff --git a/media/libstagefright/Android.mk b/media/libstagefright/Android.mk new file mode 100644 index 0000000..6e7936c --- /dev/null +++ b/media/libstagefright/Android.mk @@ -0,0 +1,56 @@ +ifeq ($(BUILD_WITH_STAGEFRIGHT),true) + +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + CachingDataSource.cpp \ + DataSource.cpp \ + FileSource.cpp \ + HTTPDataSource.cpp \ + HTTPStream.cpp \ + MP3Extractor.cpp \ + MPEG4Extractor.cpp \ + MPEG4Writer.cpp \ + MediaBuffer.cpp \ + MediaBufferGroup.cpp \ + MediaExtractor.cpp \ + MediaPlayerImpl.cpp \ + MediaSource.cpp \ + MetaData.cpp \ + MmapSource.cpp \ + QComHardwareRenderer.cpp \ + SampleTable.cpp \ + ShoutcastSource.cpp \ + SoftwareRenderer.cpp \ + SurfaceRenderer.cpp \ + TimeSource.cpp \ + TimedEventQueue.cpp \ + Utils.cpp \ + AudioPlayer.cpp \ + ESDS.cpp \ + OMXClient.cpp \ + OMXDecoder.cpp \ + string.cpp + +LOCAL_C_INCLUDES:= \ + $(TOP)/external/opencore/extern_libs_v2/khronos/openmax/include \ + $(TOP)/external/opencore/android + +LOCAL_SHARED_LIBRARIES := \ + libbinder \ + libmedia \ + libutils \ + libcutils \ + libui + +LOCAL_CFLAGS += -Wno-multichar + +LOCAL_PRELINK_MODULE:= false + +LOCAL_MODULE:= libstagefright + +include $(BUILD_SHARED_LIBRARY) + +include $(call all-makefiles-under,$(LOCAL_PATH)) +endif diff --git a/media/libstagefright/AudioPlayer.cpp b/media/libstagefright/AudioPlayer.cpp new file mode 100644 index 0000000..17c72b9 --- /dev/null +++ b/media/libstagefright/AudioPlayer.cpp @@ -0,0 +1,285 @@ +/* + * Copyright (C) 2009 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. + */ + +#undef NDEBUG +#include <assert.h> + +#define LOG_TAG "AudioPlayer" +#include <utils/Log.h> + +#include <media/AudioTrack.h> +#include <media/stagefright/AudioPlayer.h> +#include <media/stagefright/MediaSource.h> +#include <media/stagefright/MetaData.h> + +namespace android { + +AudioPlayer::AudioPlayer(const sp<MediaPlayerBase::AudioSink> &audioSink) + : mSource(NULL), + mAudioTrack(NULL), + mInputBuffer(NULL), + mSampleRate(0), + mLatencyUs(0), + mFrameSize(0), + mNumFramesPlayed(0), + mPositionTimeMediaUs(-1), + mPositionTimeRealUs(-1), + mSeeking(false), + mStarted(false), + mAudioSink(audioSink) { +} + +AudioPlayer::~AudioPlayer() { + if (mStarted) { + stop(); + } +} + +void AudioPlayer::setSource(MediaSource *source) { + assert(mSource == NULL); + mSource = source; +} + +void AudioPlayer::start() { + assert(!mStarted); + assert(mSource != NULL); + + status_t err = mSource->start(); + assert(err == OK); + + sp<MetaData> format = mSource->getFormat(); + const char *mime; + bool success = format->findCString(kKeyMIMEType, &mime); + assert(success); + assert(!strcasecmp(mime, "audio/raw")); + + success = format->findInt32(kKeySampleRate, &mSampleRate); + assert(success); + + int32_t numChannels; + success = format->findInt32(kKeyChannelCount, &numChannels); + assert(success); + + if (mAudioSink.get() != NULL) { + status_t err = mAudioSink->open( + mSampleRate, numChannels, AudioSystem::PCM_16_BIT, + DEFAULT_AUDIOSINK_BUFFERCOUNT, + &AudioPlayer::AudioSinkCallback, this); + assert(err == OK); + + mLatencyUs = (int64_t)mAudioSink->latency() * 1000; + mFrameSize = mAudioSink->frameSize(); + + mAudioSink->start(); + } else { + mAudioTrack = new AudioTrack( + AudioSystem::MUSIC, mSampleRate, AudioSystem::PCM_16_BIT, + numChannels, 8192, 0, &AudioCallback, this, 0); + + assert(mAudioTrack->initCheck() == OK); + + mLatencyUs = (int64_t)mAudioTrack->latency() * 1000; + mFrameSize = mAudioTrack->frameSize(); + + mAudioTrack->start(); + } + + mStarted = true; +} + +void AudioPlayer::pause() { + assert(mStarted); + + if (mAudioSink.get() != NULL) { + mAudioSink->pause(); + } else { + mAudioTrack->stop(); + } +} + +void AudioPlayer::resume() { + assert(mStarted); + + if (mAudioSink.get() != NULL) { + mAudioSink->start(); + } else { + mAudioTrack->start(); + } +} + +void AudioPlayer::stop() { + assert(mStarted); + + if (mAudioSink.get() != NULL) { + mAudioSink->stop(); + } else { + mAudioTrack->stop(); + + delete mAudioTrack; + mAudioTrack = NULL; + } + + // Make sure to release any buffer we hold onto so that the + // source is able to stop(). + if (mInputBuffer != NULL) { + LOGI("AudioPlayer releasing input buffer."); + + mInputBuffer->release(); + mInputBuffer = NULL; + } + + mSource->stop(); + + mNumFramesPlayed = 0; + mPositionTimeMediaUs = -1; + mPositionTimeRealUs = -1; + mSeeking = false; + mStarted = false; +} + +// static +void AudioPlayer::AudioCallback(int event, void *user, void *info) { + static_cast<AudioPlayer *>(user)->AudioCallback(event, info); +} + +// static +void AudioPlayer::AudioSinkCallback( + MediaPlayerBase::AudioSink *audioSink, + void *buffer, size_t size, void *cookie) { + AudioPlayer *me = (AudioPlayer *)cookie; + + me->fillBuffer(buffer, size); +} + +void AudioPlayer::AudioCallback(int event, void *info) { + if (event != AudioTrack::EVENT_MORE_DATA) { + return; + } + + AudioTrack::Buffer *buffer = (AudioTrack::Buffer *)info; + fillBuffer(buffer->raw, buffer->size); +} + +void AudioPlayer::fillBuffer(void *data, size_t size) { + if (mNumFramesPlayed == 0) { + LOGI("AudioCallback"); + } + + size_t size_done = 0; + size_t size_remaining = size; + while (size_remaining > 0) { + MediaSource::ReadOptions options; + + { + Mutex::Autolock autoLock(mLock); + + if (mSeeking) { + options.setSeekTo(mSeekTimeUs); + + if (mInputBuffer != NULL) { + mInputBuffer->release(); + mInputBuffer = NULL; + } + mSeeking = false; + } + } + + if (mInputBuffer == NULL) { + status_t err = mSource->read(&mInputBuffer, &options); + + assert((err == OK && mInputBuffer != NULL) + || (err != OK && mInputBuffer == NULL)); + + if (err != OK) { + memset((char *)data + size_done, 0, size_remaining); + break; + } + + int32_t units, scale; + bool success = + mInputBuffer->meta_data()->findInt32(kKeyTimeUnits, &units); + success = success && + mInputBuffer->meta_data()->findInt32(kKeyTimeScale, &scale); + assert(success); + + Mutex::Autolock autoLock(mLock); + mPositionTimeMediaUs = (int64_t)units * 1000000 / scale; + mPositionTimeRealUs = + ((mNumFramesPlayed + size_done / 4) * 1000000) / mSampleRate; // XXX + } + + if (mInputBuffer->range_length() == 0) { + mInputBuffer->release(); + mInputBuffer = NULL; + + continue; + } + + size_t copy = size_remaining; + if (copy > mInputBuffer->range_length()) { + copy = mInputBuffer->range_length(); + } + + memcpy((char *)data + size_done, + (const char *)mInputBuffer->data() + mInputBuffer->range_offset(), + copy); + + mInputBuffer->set_range(mInputBuffer->range_offset() + copy, + mInputBuffer->range_length() - copy); + + size_done += copy; + size_remaining -= copy; + } + + Mutex::Autolock autoLock(mLock); + mNumFramesPlayed += size / mFrameSize; +} + +int64_t AudioPlayer::getRealTimeUs() { + Mutex::Autolock autoLock(mLock); + return getRealTimeUsLocked(); +} + +int64_t AudioPlayer::getRealTimeUsLocked() const { + return -mLatencyUs + (mNumFramesPlayed * 1000000) / mSampleRate; +} + +int64_t AudioPlayer::getMediaTimeUs() { + Mutex::Autolock autoLock(mLock); + + return mPositionTimeMediaUs + (getRealTimeUsLocked() - mPositionTimeRealUs); +} + +bool AudioPlayer::getMediaTimeMapping( + int64_t *realtime_us, int64_t *mediatime_us) { + Mutex::Autolock autoLock(mLock); + + *realtime_us = mPositionTimeRealUs; + *mediatime_us = mPositionTimeMediaUs; + + return mPositionTimeRealUs != -1 || mPositionTimeMediaUs != -1; +} + +status_t AudioPlayer::seekTo(int64_t time_us) { + Mutex::Autolock autoLock(mLock); + + mSeeking = true; + mSeekTimeUs = time_us; + + return OK; +} + +} diff --git a/media/libstagefright/CachingDataSource.cpp b/media/libstagefright/CachingDataSource.cpp new file mode 100644 index 0000000..0fd71d5 --- /dev/null +++ b/media/libstagefright/CachingDataSource.cpp @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2009 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. + */ + +#include <media/stagefright/CachingDataSource.h> + +#undef NDEBUG +#include <assert.h> + +#include <stdlib.h> +#include <string.h> + +namespace android { + +CachingDataSource::CachingDataSource( + DataSource *source, size_t pageSize, int numPages) + : mSource(source), + mData(malloc(pageSize * numPages)), + mPageSize(pageSize), + mFirst(NULL), + mLast(NULL) { + for (int i = 0; i < numPages; ++i) { + Page *page = new Page; + page->mPrev = mLast; + page->mNext = NULL; + + if (mLast == NULL) { + mFirst = page; + } else { + mLast->mNext = page; + } + + mLast = page; + + page->mOffset = -1; + page->mLength = 0; + page->mData = (char *)mData + mPageSize * i; + } +} + +CachingDataSource::~CachingDataSource() { + Page *page = mFirst; + while (page != NULL) { + Page *next = page->mNext; + delete page; + page = next; + } + mFirst = mLast = NULL; + + free(mData); + mData = NULL; + + delete mSource; + mSource = NULL; +} + +status_t CachingDataSource::InitCheck() const { + return OK; +} + +ssize_t CachingDataSource::read_at(off_t offset, void *data, size_t size) { + Mutex::Autolock autoLock(mLock); + + size_t total = 0; + while (size > 0) { + Page *page = mFirst; + while (page != NULL) { + if (page->mOffset >= 0 && offset >= page->mOffset + && offset < page->mOffset + page->mLength) { + break; + } + page = page->mNext; + } + + if (page == NULL) { + page = allocate_page(); + page->mOffset = offset - offset % mPageSize; + ssize_t n = mSource->read_at(page->mOffset, page->mData, mPageSize); + if (n < 0) { + page->mLength = 0; + } else { + page->mLength = (size_t)n; + } + mFirst->mPrev = page; + page->mNext = mFirst; + page->mPrev = NULL; + mFirst = page; + + if (n < 0) { + return n; + } + + if (offset >= page->mOffset + page->mLength) { + break; + } + } else { + // Move "page" to the front in LRU order. + if (page->mNext != NULL) { + page->mNext->mPrev = page->mPrev; + } else { + mLast = page->mPrev; + } + + if (page->mPrev != NULL) { + page->mPrev->mNext = page->mNext; + } else { + mFirst = page->mNext; + } + + mFirst->mPrev = page; + page->mNext = mFirst; + page->mPrev = NULL; + mFirst = page; + } + + size_t copy = page->mLength - (offset - page->mOffset); + if (copy > size) { + copy = size; + } + memcpy(data,(const char *)page->mData + (offset - page->mOffset), + copy); + + total += copy; + + if (page->mLength < mPageSize) { + // This was the final page. There is no more data beyond it. + break; + } + + offset += copy; + size -= copy; + data = (char *)data + copy; + } + + return total; +} + +CachingDataSource::Page *CachingDataSource::allocate_page() { + // The last page is the least recently used, i.e. oldest. + + Page *page = mLast; + + page->mPrev->mNext = NULL; + mLast = page->mPrev; + page->mPrev = NULL; + + return page; +} + +} // namespace android diff --git a/media/libstagefright/CameraSource.cpp b/media/libstagefright/CameraSource.cpp new file mode 100644 index 0000000..ee12873 --- /dev/null +++ b/media/libstagefright/CameraSource.cpp @@ -0,0 +1,226 @@ +/* + * Copyright (C) 2009 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. + */ + +#include <sys/time.h> + +#undef NDEBUG +#include <assert.h> + +#include <OMX_Component.h> + +#include <binder/IServiceManager.h> +#include <media/stagefright/CameraSource.h> +#include <media/stagefright/MediaErrors.h> +#include <media/stagefright/MetaData.h> +#include <ui/ICameraClient.h> +#include <ui/ICameraService.h> +#include <ui/Overlay.h> +#include <utils/String16.h> + +namespace android { + +class CameraBuffer : public MediaBuffer { +public: + CameraBuffer(const sp<IMemory> &frame) + : MediaBuffer(frame->pointer(), frame->size()), + mFrame(frame) { + } + + sp<IMemory> releaseFrame() { + sp<IMemory> frame = mFrame; + mFrame.clear(); + return frame; + } + +private: + sp<IMemory> mFrame; +}; + +class CameraSourceClient : public BnCameraClient { +public: + CameraSourceClient() + : mSource(NULL) { + } + + virtual void notifyCallback(int32_t msgType, int32_t ext1, int32_t ext2) { + assert(mSource != NULL); + mSource->notifyCallback(msgType, ext1, ext2); + } + + virtual void dataCallback(int32_t msgType, const sp<IMemory> &data) { + assert(mSource != NULL); + mSource->dataCallback(msgType, data); + } + + void setCameraSource(CameraSource *source) { + mSource = source; + } + +private: + CameraSource *mSource; +}; + +class DummySurface : public BnSurface { +public: + DummySurface() {} + + virtual status_t registerBuffers(const BufferHeap &buffers) { + return OK; + } + + virtual void postBuffer(ssize_t offset) { + } + + virtual void unregisterBuffers() { + } + + virtual sp<OverlayRef> createOverlay( + uint32_t w, uint32_t h, int32_t format) { + return NULL; + } +}; + +// static +CameraSource *CameraSource::Create() { + sp<IServiceManager> sm = defaultServiceManager(); + + sp<ICameraService> service = + interface_cast<ICameraService>( + sm->getService(String16("media.camera"))); + + sp<CameraSourceClient> client = new CameraSourceClient; + sp<ICamera> camera = service->connect(client); + + CameraSource *source = new CameraSource(camera, client); + client->setCameraSource(source); + + return source; +} + +CameraSource::CameraSource( + const sp<ICamera> &camera, const sp<ICameraClient> &client) + : mCamera(camera), + mCameraClient(client), + mNumFrames(0), + mStarted(false) { + printf("params: \"%s\"\n", mCamera->getParameters().string()); +} + +CameraSource::~CameraSource() { + if (mStarted) { + stop(); + } + + mCamera->disconnect(); +} + +status_t CameraSource::start(MetaData *) { + assert(!mStarted); + + status_t err = mCamera->lock(); + assert(err == OK); + + err = mCamera->setPreviewDisplay(new DummySurface); + assert(err == OK); + mCamera->setPreviewCallbackFlag(1); + mCamera->startPreview(); + assert(err == OK); + + mStarted = true; + + return OK; +} + +status_t CameraSource::stop() { + assert(mStarted); + + mCamera->stopPreview(); + mCamera->unlock(); + + mStarted = false; + + return OK; +} + +sp<MetaData> CameraSource::getFormat() { + sp<MetaData> meta = new MetaData; + meta->setCString(kKeyMIMEType, "video/raw"); + meta->setInt32(kKeyColorFormat, OMX_COLOR_FormatYUV420SemiPlanar); + meta->setInt32(kKeyWidth, 480); + meta->setInt32(kKeyHeight, 320); + + return meta; +} + +status_t CameraSource::read( + MediaBuffer **buffer, const ReadOptions *options) { + assert(mStarted); + + *buffer = NULL; + + int64_t seekTimeUs; + if (options && options->getSeekTo(&seekTimeUs)) { + return ERROR_UNSUPPORTED; + } + + sp<IMemory> frame; + + { + Mutex::Autolock autoLock(mLock); + while (mFrames.empty()) { + mFrameAvailableCondition.wait(mLock); + } + + frame = *mFrames.begin(); + mFrames.erase(mFrames.begin()); + } + + int count = mNumFrames++; + + *buffer = new CameraBuffer(frame); + + (*buffer)->meta_data()->clear(); + (*buffer)->meta_data()->setInt32(kKeyTimeScale, 15); + (*buffer)->meta_data()->setInt32(kKeyTimeUnits, count); + + (*buffer)->add_ref(); + (*buffer)->setObserver(this); + + return OK; +} + +void CameraSource::notifyCallback(int32_t msgType, int32_t ext1, int32_t ext2) { + printf("notifyCallback %d, %d, %d\n", msgType, ext1, ext2); +} + +void CameraSource::dataCallback(int32_t msgType, const sp<IMemory> &data) { + Mutex::Autolock autoLock(mLock); + + mFrames.push_back(data); + mFrameAvailableCondition.signal(); +} + +void CameraSource::signalBufferReturned(MediaBuffer *_buffer) { + CameraBuffer *buffer = static_cast<CameraBuffer *>(_buffer); + + mCamera->releaseRecordingFrame(buffer->releaseFrame()); + + buffer->setObserver(NULL); + buffer->release(); + buffer = NULL; +} + +} // namespace android diff --git a/media/libstagefright/DataSource.cpp b/media/libstagefright/DataSource.cpp new file mode 100644 index 0000000..6e6b43d --- /dev/null +++ b/media/libstagefright/DataSource.cpp @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2009 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. + */ + +#include <media/stagefright/DataSource.h> +#include <media/stagefright/MediaErrors.h> +#include <media/stagefright/MP3Extractor.h> +#include <media/stagefright/MPEG4Extractor.h> +#include <utils/String8.h> + +namespace android { + +status_t DataSource::getSize(off_t *size) { + *size = 0; + + return ERROR_UNSUPPORTED; +} + +//////////////////////////////////////////////////////////////////////////////// + +Mutex DataSource::gSnifferMutex; +List<DataSource::SnifferFunc> DataSource::gSniffers; + +bool DataSource::sniff(String8 *mimeType, float *confidence) { + *mimeType = ""; + *confidence = 0.0f; + + Mutex::Autolock autoLock(gSnifferMutex); + for (List<SnifferFunc>::iterator it = gSniffers.begin(); + it != gSniffers.end(); ++it) { + String8 newMimeType; + float newConfidence; + if ((*it)(this, &newMimeType, &newConfidence)) { + if (newConfidence > *confidence) { + *mimeType = newMimeType; + *confidence = newConfidence; + } + } + } + + return *confidence > 0.0; +} + +// static +void DataSource::RegisterSniffer(SnifferFunc func) { + Mutex::Autolock autoLock(gSnifferMutex); + + for (List<SnifferFunc>::iterator it = gSniffers.begin(); + it != gSniffers.end(); ++it) { + if (*it == func) { + return; + } + } + + gSniffers.push_back(func); +} + +// static +void DataSource::RegisterDefaultSniffers() { + RegisterSniffer(SniffMP3); + RegisterSniffer(SniffMPEG4); +} + +} // namespace android diff --git a/media/libstagefright/ESDS.cpp b/media/libstagefright/ESDS.cpp new file mode 100644 index 0000000..53b92a0 --- /dev/null +++ b/media/libstagefright/ESDS.cpp @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2009 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. + */ + +#include <media/stagefright/ESDS.h> + +#include <string.h> + +namespace android { + +ESDS::ESDS(const void *data, size_t size) + : mData(new uint8_t[size]), + mSize(size), + mInitCheck(NO_INIT), + mDecoderSpecificOffset(0), + mDecoderSpecificLength(0) { + memcpy(mData, data, size); + + mInitCheck = parse(); +} + +ESDS::~ESDS() { + delete[] mData; + mData = NULL; +} + +status_t ESDS::InitCheck() const { + return mInitCheck; +} + +status_t ESDS::getCodecSpecificInfo(const void **data, size_t *size) const { + if (mInitCheck != OK) { + return mInitCheck; + } + + *data = &mData[mDecoderSpecificOffset]; + *size = mDecoderSpecificLength; + + return OK; +} + +status_t ESDS::skipDescriptorHeader( + size_t offset, size_t size, + uint8_t *tag, size_t *data_offset, size_t *data_size) const { + if (size == 0) { + return ERROR_MALFORMED; + } + + *tag = mData[offset++]; + --size; + + *data_size = 0; + bool more; + do { + if (size == 0) { + return ERROR_MALFORMED; + } + + uint8_t x = mData[offset++]; + --size; + + *data_size = (*data_size << 7) | (x & 0x7f); + more = (x & 0x80) != 0; + } + while (more); + + if (*data_size > size) { + return ERROR_MALFORMED; + } + + *data_offset = offset; + + return OK; +} + +status_t ESDS::parse() { + uint8_t tag; + size_t data_offset; + size_t data_size; + status_t err = + skipDescriptorHeader(0, mSize, &tag, &data_offset, &data_size); + + if (err != OK) { + return err; + } + + if (tag != kTag_ESDescriptor) { + return ERROR_MALFORMED; + } + + return parseESDescriptor(data_offset, data_size); +} + +status_t ESDS::parseESDescriptor(size_t offset, size_t size) { + if (size < 3) { + return ERROR_MALFORMED; + } + + offset += 2; // skip ES_ID + size -= 2; + + unsigned streamDependenceFlag = mData[offset] & 0x80; + unsigned URL_Flag = mData[offset] & 0x40; + unsigned OCRstreamFlag = mData[offset] & 0x20; + + ++offset; + --size; + + if (streamDependenceFlag) { + offset += 2; + size -= 2; + } + + if (URL_Flag) { + if (offset >= size) { + return ERROR_MALFORMED; + } + unsigned URLlength = mData[offset]; + offset += URLlength + 1; + size -= URLlength + 1; + } + + if (OCRstreamFlag) { + offset += 2; + size -= 2; + } + + if (offset >= size) { + return ERROR_MALFORMED; + } + + uint8_t tag; + size_t sub_offset, sub_size; + status_t err = skipDescriptorHeader( + offset, size, &tag, &sub_offset, &sub_size); + + if (err != OK) { + return err; + } + + if (tag != kTag_DecoderConfigDescriptor) { + return ERROR_MALFORMED; + } + + err = parseDecoderConfigDescriptor(sub_offset, sub_size); + + return err; +} + +status_t ESDS::parseDecoderConfigDescriptor(size_t offset, size_t size) { + if (size < 13) { + return ERROR_MALFORMED; + } + + offset += 13; + size -= 13; + + if (size == 0) { + mDecoderSpecificOffset = 0; + mDecoderSpecificLength = 0; + return OK; + } + + uint8_t tag; + size_t sub_offset, sub_size; + status_t err = skipDescriptorHeader( + offset, size, &tag, &sub_offset, &sub_size); + + if (err != OK) { + return err; + } + + if (tag != kTag_DecoderSpecificInfo) { + return ERROR_MALFORMED; + } + + mDecoderSpecificOffset = sub_offset; + mDecoderSpecificLength = sub_size; + + return OK; +} + +} // namespace android + diff --git a/media/libstagefright/FileSource.cpp b/media/libstagefright/FileSource.cpp new file mode 100644 index 0000000..c26d0a0 --- /dev/null +++ b/media/libstagefright/FileSource.cpp @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2009 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. + */ + +#include <media/stagefright/FileSource.h> + +#undef NDEBUG +#include <assert.h> + +namespace android { + +FileSource::FileSource(const char *filename) + : mFile(fopen(filename, "rb")) { +} + +FileSource::~FileSource() { + if (mFile != NULL) { + fclose(mFile); + mFile = NULL; + } +} + +status_t FileSource::InitCheck() const { + return mFile != NULL ? OK : NO_INIT; +} + +ssize_t FileSource::read_at(off_t offset, void *data, size_t size) { + Mutex::Autolock autoLock(mLock); + + int err = fseeko(mFile, offset, SEEK_SET); + assert(err != -1); + + ssize_t result = fread(data, 1, size, mFile); + + return result; +} + +} // namespace android diff --git a/media/libstagefright/HTTPDataSource.cpp b/media/libstagefright/HTTPDataSource.cpp new file mode 100644 index 0000000..d1f8cd4 --- /dev/null +++ b/media/libstagefright/HTTPDataSource.cpp @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2009 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. + */ + +#undef NDEBUG +#include <assert.h> + +#include <stdlib.h> + +#include <media/stagefright/HTTPDataSource.h> +#include <media/stagefright/HTTPStream.h> +#include <media/stagefright/string.h> + +namespace android { + +HTTPDataSource::HTTPDataSource(const char *uri) + : mHost(NULL), + mPort(0), + mPath(NULL), + mBuffer(malloc(kBufferSize)), + mBufferLength(0), + mBufferOffset(0) { + assert(!strncasecmp("http://", uri, 7)); + + string host; + string path; + int port; + + char *slash = strchr(uri + 7, '/'); + if (slash == NULL) { + host = uri + 7; + path = "/"; + } else { + host = string(uri + 7, slash - (uri + 7)); + path = slash; + } + + char *colon = strchr(host.c_str(), ':'); + if (colon == NULL) { + port = 80; + } else { + char *end; + long tmp = strtol(colon + 1, &end, 10); + assert(end > colon + 1); + assert(tmp > 0 && tmp < 65536); + port = tmp; + + host = string(host, 0, colon - host.c_str()); + } + + LOGI("Connecting to host '%s', port %d, path '%s'", + host.c_str(), port, path.c_str()); + + mHost = strdup(host.c_str()); + mPort = port; + mPath = strdup(path.c_str()); + + status_t err = mHttp.connect(mHost, mPort); + assert(err == OK); +} + +HTTPDataSource::HTTPDataSource(const char *host, int port, const char *path) + : mHost(strdup(host)), + mPort(port), + mPath(strdup(path)), + mBuffer(malloc(kBufferSize)), + mBufferLength(0), + mBufferOffset(0) { + status_t err = mHttp.connect(mHost, mPort); + assert(err == OK); +} + +HTTPDataSource::~HTTPDataSource() { + mHttp.disconnect(); + + free(mBuffer); + mBuffer = NULL; + + free(mPath); + mPath = NULL; +} + +ssize_t HTTPDataSource::read_at(off_t offset, void *data, size_t size) { + if (offset >= mBufferOffset && offset < mBufferOffset + mBufferLength) { + size_t num_bytes_available = mBufferLength - (offset - mBufferOffset); + + size_t copy = num_bytes_available; + if (copy > size) { + copy = size; + } + + memcpy(data, (const char *)mBuffer + (offset - mBufferOffset), copy); + + return copy; + } + + mBufferOffset = offset; + mBufferLength = 0; + + char host[128]; + sprintf(host, "Host: %s\r\n", mHost); + + char range[128]; + sprintf(range, "Range: bytes=%ld-%ld\r\n\r\n", + mBufferOffset, mBufferOffset + kBufferSize - 1); + + int http_status; + + status_t err; + int attempt = 1; + for (;;) { + if ((err = mHttp.send("GET ")) != OK + || (err = mHttp.send(mPath)) != OK + || (err = mHttp.send(" HTTP/1.1\r\n")) != OK + || (err = mHttp.send(host)) != OK + || (err = mHttp.send(range)) != OK + || (err = mHttp.send("\r\n")) != OK + || (err = mHttp.receive_header(&http_status)) != OK) { + + if (attempt == 3) { + return err; + } + + mHttp.connect(mHost, mPort); + ++attempt; + } else { + break; + } + } + + if ((http_status / 100) != 2) { + return UNKNOWN_ERROR; + } + + string value; + if (!mHttp.find_header_value("Content-Length", &value)) { + return UNKNOWN_ERROR; + } + + char *end; + unsigned long contentLength = strtoul(value.c_str(), &end, 10); + + ssize_t num_bytes_received = mHttp.receive(mBuffer, contentLength); + + if (num_bytes_received <= 0) { + return num_bytes_received; + } + + mBufferLength = (size_t)num_bytes_received; + + size_t copy = mBufferLength; + if (copy > size) { + copy = size; + } + + memcpy(data, mBuffer, copy); + + return copy; +} + +} // namespace android + diff --git a/media/libstagefright/HTTPStream.cpp b/media/libstagefright/HTTPStream.cpp new file mode 100644 index 0000000..29e6f72 --- /dev/null +++ b/media/libstagefright/HTTPStream.cpp @@ -0,0 +1,285 @@ +/* + * Copyright (C) 2009 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. + */ + +#include <sys/socket.h> + +#include <arpa/inet.h> +#include <assert.h> +#include <ctype.h> +#include <errno.h> +#include <netdb.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <media/stagefright/HTTPStream.h> + +namespace android { + +// static +const char *HTTPStream::kStatusKey = ":status:"; + +HTTPStream::HTTPStream() + : mState(READY), + mSocket(-1) { +} + +HTTPStream::~HTTPStream() { + disconnect(); +} + +status_t HTTPStream::connect(const char *server, int port) { + status_t err = OK; + + if (mState == CONNECTED) { + return ERROR_ALREADY_CONNECTED; + } + + assert(mSocket == -1); + mSocket = socket(AF_INET, SOCK_STREAM, 0); + + if (mSocket < 0) { + return UNKNOWN_ERROR; + } + + struct hostent *ent = gethostbyname(server); + if (ent == NULL) { + err = ERROR_UNKNOWN_HOST; + goto exit1; + } + + struct sockaddr_in addr; + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + addr.sin_addr.s_addr = *(in_addr_t *)ent->h_addr; + memset(addr.sin_zero, 0, sizeof(addr.sin_zero)); + + if (::connect(mSocket, (const struct sockaddr *)&addr, sizeof(addr)) < 0) { + err = ERROR_CANNOT_CONNECT; + goto exit1; + } + + mState = CONNECTED; + + return OK; + +exit1: + close(mSocket); + mSocket = -1; + + return err; +} + +status_t HTTPStream::disconnect() { + if (mState != CONNECTED) { + return ERROR_NOT_CONNECTED; + } + + assert(mSocket >= 0); + close(mSocket); + mSocket = -1; + + mState = READY; + + return OK; +} + +status_t HTTPStream::send(const char *data, size_t size) { + if (mState != CONNECTED) { + return ERROR_NOT_CONNECTED; + } + + while (size > 0) { + ssize_t n = ::send(mSocket, data, size, 0); + + if (n < 0) { + if (errno == EINTR) { + continue; + } + + disconnect(); + + return ERROR_IO; + } else if (n == 0) { + disconnect(); + + return ERROR_CONNECTION_LOST; + } + + size -= (size_t)n; + data += (size_t)n; + } + + return OK; +} + +status_t HTTPStream::send(const char *data) { + return send(data, strlen(data)); +} + +status_t HTTPStream::receive_line(char *line, size_t size) { + if (mState != CONNECTED) { + return ERROR_NOT_CONNECTED; + } + + bool saw_CR = false; + size_t length = 0; + + for (;;) { + char c; + ssize_t n = recv(mSocket, &c, 1, 0); + if (n < 0) { + if (errno == EINTR) { + continue; + } + + disconnect(); + + return ERROR_IO; + } else if (n == 0) { + disconnect(); + + return ERROR_CONNECTION_LOST; + } + + if (saw_CR && c == '\n') { + // We have a complete line. + + line[length - 1] = '\0'; + return OK; + } + + saw_CR = (c == '\r'); + + assert(length + 1 < size); + line[length++] = c; + } +} + +status_t HTTPStream::receive_header(int *http_status) { + *http_status = -1; + mHeaders.clear(); + + char line[256]; + status_t err = receive_line(line, sizeof(line)); + if (err != OK) { + return err; + } + + mHeaders.add(string(kStatusKey), string(line)); + + char *spacePos = strchr(line, ' '); + if (spacePos == NULL) { + // Malformed response? + return UNKNOWN_ERROR; + } + + char *status_start = spacePos + 1; + char *status_end = status_start; + while (isdigit(*status_end)) { + ++status_end; + } + + if (status_end == status_start) { + // Malformed response, status missing? + return UNKNOWN_ERROR; + } + + memmove(line, status_start, status_end - status_start); + line[status_end - status_start] = '\0'; + + long tmp = strtol(line, NULL, 10); + if (tmp < 0 || tmp > 999) { + return UNKNOWN_ERROR; + } + + *http_status = (int)tmp; + + for (;;) { + err = receive_line(line, sizeof(line)); + if (err != OK) { + return err; + } + + if (*line == '\0') { + // Empty line signals the end of the header. + break; + } + + // puts(line); + + char *colonPos = strchr(line, ':'); + if (colonPos == NULL) { + mHeaders.add(string(line), string()); + } else { + char *end_of_key = colonPos; + while (end_of_key > line && isspace(end_of_key[-1])) { + --end_of_key; + } + + char *start_of_value = colonPos + 1; + while (isspace(*start_of_value)) { + ++start_of_value; + } + + *end_of_key = '\0'; + + mHeaders.add(string(line), string(start_of_value)); + } + } + + return OK; +} + +ssize_t HTTPStream::receive(void *data, size_t size) { + size_t total = 0; + while (total < size) { + ssize_t n = recv(mSocket, (char *)data + total, size - total, 0); + + if (n < 0) { + if (errno == EINTR) { + continue; + } + + disconnect(); + return ERROR_IO; + } else if (n == 0) { + disconnect(); + + return ERROR_CONNECTION_LOST; + } + + total += (size_t)n; + } + + return (ssize_t)total; +} + +bool HTTPStream::find_header_value(const string &key, string *value) const { + ssize_t index = mHeaders.indexOfKey(key); + if (index < 0) { + value->clear(); + return false; + } + + *value = mHeaders.valueAt(index); + + return true; +} + +} // namespace android + diff --git a/media/libstagefright/MP3Extractor.cpp b/media/libstagefright/MP3Extractor.cpp new file mode 100644 index 0000000..74f37b1 --- /dev/null +++ b/media/libstagefright/MP3Extractor.cpp @@ -0,0 +1,522 @@ +/* + * Copyright (C) 2009 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 "MP3Extractor" +#include <utils/Log.h> + +#undef NDEBUG +#include <assert.h> + +#include <media/stagefright/DataSource.h> +#include <media/stagefright/MP3Extractor.h> +#include <media/stagefright/MediaBuffer.h> +#include <media/stagefright/MediaBufferGroup.h> +#include <media/stagefright/MediaErrors.h> +#include <media/stagefright/MediaSource.h> +#include <media/stagefright/MetaData.h> +#include <media/stagefright/Utils.h> +#include <utils/String8.h> + +namespace android { + +static bool get_mp3_frame_size( + uint32_t header, size_t *frame_size, + int *out_sampling_rate = NULL, int *out_channels = NULL, + int *out_bitrate = NULL) { + *frame_size = 0; + + if (out_sampling_rate) { + *out_sampling_rate = 0; + } + + if (out_channels) { + *out_channels = 0; + } + + if (out_bitrate) { + *out_bitrate = 0; + } + + if ((header & 0xffe00000) != 0xffe00000) { + return false; + } + + unsigned version = (header >> 19) & 3; + + if (version == 0x01) { + return false; + } + + unsigned layer = (header >> 17) & 3; + + if (layer == 0x00) { + return false; + } + + unsigned protection = (header >> 16) & 1; + + unsigned bitrate_index = (header >> 12) & 0x0f; + + if (bitrate_index == 0 || bitrate_index == 0x0f) { + // Disallow "free" bitrate. + return false; + } + + unsigned sampling_rate_index = (header >> 10) & 3; + + if (sampling_rate_index == 3) { + return false; + } + + static const int kSamplingRateV1[] = { 44100, 48000, 32000 }; + int sampling_rate = kSamplingRateV1[sampling_rate_index]; + if (version == 2 /* V2 */) { + sampling_rate /= 2; + } else if (version == 0 /* V2.5 */) { + sampling_rate /= 4; + } + + unsigned padding = (header >> 9) & 1; + + if (layer == 3) { + // layer I + + static const int kBitrateV1[] = { + 32, 64, 96, 128, 160, 192, 224, 256, + 288, 320, 352, 384, 416, 448 + }; + + static const int kBitrateV2[] = { + 32, 48, 56, 64, 80, 96, 112, 128, + 144, 160, 176, 192, 224, 256 + }; + + int bitrate = + (version == 3 /* V1 */) + ? kBitrateV1[bitrate_index - 1] + : kBitrateV2[bitrate_index - 1]; + + if (out_bitrate) { + *out_bitrate = bitrate; + } + + *frame_size = (12000 * bitrate / sampling_rate + padding) * 4; + } else { + // layer II or III + + static const int kBitrateV1L2[] = { + 32, 48, 56, 64, 80, 96, 112, 128, + 160, 192, 224, 256, 320, 384 + }; + + static const int kBitrateV1L3[] = { + 32, 40, 48, 56, 64, 80, 96, 112, + 128, 160, 192, 224, 256, 320 + }; + + static const int kBitrateV2[] = { + 8, 16, 24, 32, 40, 48, 56, 64, + 80, 96, 112, 128, 144, 160 + }; + + int bitrate; + if (version == 3 /* V1 */) { + bitrate = (layer == 2 /* L2 */) + ? kBitrateV1L2[bitrate_index - 1] + : kBitrateV1L3[bitrate_index - 1]; + } else { + // V2 (or 2.5) + + bitrate = kBitrateV2[bitrate_index - 1]; + } + + if (out_bitrate) { + *out_bitrate = bitrate; + } + + *frame_size = 144000 * bitrate / sampling_rate + padding; + } + + if (out_sampling_rate) { + *out_sampling_rate = sampling_rate; + } + + if (out_channels) { + int channel_mode = (header >> 6) & 3; + + *out_channels = (channel_mode == 3) ? 1 : 2; + } + + return true; +} + +static bool Resync( + DataSource *source, uint32_t match_header, + off_t *inout_pos, uint32_t *out_header) { + // Everything must match except for + // protection, bitrate, padding, private bits and mode extension. + const uint32_t kMask = 0xfffe0ccf; + + const size_t kMaxFrameSize = 4096; + uint8_t *buffer = new uint8_t[kMaxFrameSize]; + + off_t pos = *inout_pos; + size_t buffer_offset = kMaxFrameSize; + size_t buffer_length = kMaxFrameSize; + bool valid = false; + do { + if (buffer_offset + 3 >= buffer_length) { + if (buffer_length < kMaxFrameSize) { + break; + } + + pos += buffer_length; + + if (pos >= *inout_pos + 128 * 1024) { + // Don't scan forever. + LOGV("giving up at offset %ld", pos); + break; + } + + memmove(buffer, &buffer[buffer_offset], buffer_length - buffer_offset); + buffer_length = buffer_length - buffer_offset; + buffer_offset = 0; + + ssize_t n = source->read_at( + pos, &buffer[buffer_length], kMaxFrameSize - buffer_length); + + if (n <= 0) { + break; + } + + buffer_length += (size_t)n; + + continue; + } + + uint32_t header = U32_AT(&buffer[buffer_offset]); + + if (match_header != 0 && (header & kMask) != (match_header & kMask)) { + ++buffer_offset; + continue; + } + + size_t frame_size; + int sample_rate, num_channels, bitrate; + if (get_mp3_frame_size(header, &frame_size, + &sample_rate, &num_channels, &bitrate)) { + LOGV("found possible 1st frame at %ld", pos + buffer_offset); + + // We found what looks like a valid frame, + // now find its successors. + + off_t test_pos = pos + buffer_offset + frame_size; + + valid = true; + for (int j = 0; j < 3; ++j) { + uint8_t tmp[4]; + if (source->read_at(test_pos, tmp, 4) < 4) { + valid = false; + break; + } + + uint32_t test_header = U32_AT(tmp); + + LOGV("subsequent header is %08x", test_header); + + if ((test_header & kMask) != (header & kMask)) { + valid = false; + break; + } + + size_t test_frame_size; + if (!get_mp3_frame_size(test_header, &test_frame_size)) { + valid = false; + break; + } + + LOGV("found subsequent frame #%d at %ld", j + 2, test_pos); + + test_pos += test_frame_size; + } + } + + if (valid) { + *inout_pos = pos + buffer_offset; + + if (out_header != NULL) { + *out_header = header; + } + } else { + LOGV("no dice, no valid sequence of frames found."); + } + + ++buffer_offset; + + } while (!valid); + + delete[] buffer; + buffer = NULL; + + return valid; +} + +class MP3Source : public MediaSource { +public: + MP3Source( + const sp<MetaData> &meta, DataSource *source, + off_t first_frame_pos, uint32_t fixed_header); + + virtual ~MP3Source(); + + virtual status_t start(MetaData *params = NULL); + virtual status_t stop(); + + virtual sp<MetaData> getFormat(); + + virtual status_t read( + MediaBuffer **buffer, const ReadOptions *options = NULL); + +private: + sp<MetaData> mMeta; + DataSource *mDataSource; + off_t mFirstFramePos; + uint32_t mFixedHeader; + off_t mCurrentPos; + int64_t mCurrentTimeUs; + bool mStarted; + + MediaBufferGroup *mGroup; + + MP3Source(const MP3Source &); + MP3Source &operator=(const MP3Source &); +}; + +MP3Extractor::MP3Extractor(DataSource *source) + : mDataSource(source), + mFirstFramePos(-1), + mFixedHeader(0) { + off_t pos = 0; + uint32_t header; + bool success = Resync(mDataSource, 0, &pos, &header); + assert(success); + + if (success) { + mFirstFramePos = pos; + mFixedHeader = header; + + size_t frame_size; + int sample_rate; + int num_channels; + int bitrate; + get_mp3_frame_size( + header, &frame_size, &sample_rate, &num_channels, &bitrate); + + mMeta = new MetaData; + + mMeta->setCString(kKeyMIMEType, "audio/mpeg"); + mMeta->setInt32(kKeySampleRate, sample_rate); + mMeta->setInt32(kKeyBitRate, bitrate); + mMeta->setInt32(kKeyChannelCount, num_channels); + + off_t fileSize; + if (mDataSource->getSize(&fileSize) == OK) { + mMeta->setInt32( + kKeyDuration, + 8 * (fileSize - mFirstFramePos) / bitrate); + mMeta->setInt32(kKeyTimeScale, 1000); + } + } +} + +MP3Extractor::~MP3Extractor() { + delete mDataSource; + mDataSource = NULL; +} + +status_t MP3Extractor::countTracks(int *num_tracks) { + *num_tracks = mFirstFramePos < 0 ? 0 : 1; + + return OK; +} + +status_t MP3Extractor::getTrack(int index, MediaSource **source) { + if (mFirstFramePos < 0 || index != 0) { + return ERROR_OUT_OF_RANGE; + } + + *source = new MP3Source( + mMeta, mDataSource, mFirstFramePos, mFixedHeader); + + return OK; +} + +sp<MetaData> MP3Extractor::getTrackMetaData(int index) { + if (mFirstFramePos < 0 || index != 0) { + return NULL; + } + + return mMeta; +} + +//////////////////////////////////////////////////////////////////////////////// + +MP3Source::MP3Source( + const sp<MetaData> &meta, DataSource *source, + off_t first_frame_pos, uint32_t fixed_header) + : mMeta(meta), + mDataSource(source), + mFirstFramePos(first_frame_pos), + mFixedHeader(fixed_header), + mCurrentPos(0), + mCurrentTimeUs(0), + mStarted(false), + mGroup(NULL) { +} + +MP3Source::~MP3Source() { + if (mStarted) { + stop(); + } +} + +status_t MP3Source::start(MetaData *) { + assert(!mStarted); + + mGroup = new MediaBufferGroup; + + const size_t kMaxFrameSize = 32768; + mGroup->add_buffer(new MediaBuffer(kMaxFrameSize)); + + mCurrentPos = mFirstFramePos; + mCurrentTimeUs = 0; + + mStarted = true; + + return OK; +} + +status_t MP3Source::stop() { + assert(mStarted); + + delete mGroup; + mGroup = NULL; + + mStarted = false; + + return OK; +} + +sp<MetaData> MP3Source::getFormat() { + return mMeta; +} + +status_t MP3Source::read( + MediaBuffer **out, const ReadOptions *options) { + *out = NULL; + + int64_t seekTimeUs; + if (options != NULL && options->getSeekTo(&seekTimeUs)) { + int32_t bitrate; + if (!mMeta->findInt32(kKeyBitRate, &bitrate)) { + // bitrate is in kbits/sec. + LOGI("no bitrate"); + + return ERROR_UNSUPPORTED; + } + + mCurrentTimeUs = seekTimeUs; + mCurrentPos = mFirstFramePos + seekTimeUs * bitrate / 1000000 * 125; + } + + MediaBuffer *buffer; + status_t err = mGroup->acquire_buffer(&buffer); + if (err != OK) { + return err; + } + + size_t frame_size; + for (;;) { + ssize_t n = mDataSource->read_at(mCurrentPos, buffer->data(), 4); + if (n < 4) { + buffer->release(); + buffer = NULL; + + return ERROR_END_OF_STREAM; + } + + uint32_t header = U32_AT((const uint8_t *)buffer->data()); + + if (get_mp3_frame_size(header, &frame_size)) { + break; + } + + // Lost sync. + LOGW("lost sync!\n"); + + off_t pos = mCurrentPos; + if (!Resync(mDataSource, mFixedHeader, &pos, NULL)) { + LOGE("Unable to resync. Signalling end of stream."); + + buffer->release(); + buffer = NULL; + + return ERROR_END_OF_STREAM; + } + + mCurrentPos = pos; + + // Try again with the new position. + } + + assert(frame_size <= buffer->size()); + + ssize_t n = mDataSource->read_at(mCurrentPos, buffer->data(), frame_size); + if (n < (ssize_t)frame_size) { + buffer->release(); + buffer = NULL; + + return ERROR_END_OF_STREAM; + } + + buffer->set_range(0, frame_size); + + buffer->meta_data()->setInt32(kKeyTimeUnits, mCurrentTimeUs / 1000); + buffer->meta_data()->setInt32(kKeyTimeScale, 1000); + + mCurrentPos += frame_size; + mCurrentTimeUs += 1152 * 1000000 / 44100; + + *out = buffer; + + return OK; +} + +bool SniffMP3(DataSource *source, String8 *mimeType, float *confidence) { + off_t pos = 0; + uint32_t header; + if (!Resync(source, 0, &pos, &header)) { + return false; + } + + *mimeType = "audio/mpeg"; + *confidence = 0.3f; + + return true; +} + +} // namespace android diff --git a/media/libstagefright/MPEG4Extractor.cpp b/media/libstagefright/MPEG4Extractor.cpp new file mode 100644 index 0000000..caaec06 --- /dev/null +++ b/media/libstagefright/MPEG4Extractor.cpp @@ -0,0 +1,937 @@ +/* + * Copyright (C) 2009 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_TAG "MPEG4Extractor" +#include <utils/Log.h> + +#include <arpa/inet.h> + +#undef NDEBUG +#include <assert.h> + +#include <ctype.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> + +#include <media/stagefright/DataSource.h> +#include <media/stagefright/MPEG4Extractor.h> +#include <media/stagefright/MediaBuffer.h> +#include <media/stagefright/MediaBufferGroup.h> +#include <media/stagefright/MediaSource.h> +#include <media/stagefright/MetaData.h> +#include <media/stagefright/SampleTable.h> +#include <media/stagefright/Utils.h> +#include <utils/String8.h> + +namespace android { + +class MPEG4Source : public MediaSource { +public: + // Caller retains ownership of both "dataSource" and "sampleTable". + MPEG4Source(const sp<MetaData> &format, DataSource *dataSource, + SampleTable *sampleTable); + + virtual ~MPEG4Source(); + + virtual status_t start(MetaData *params = NULL); + virtual status_t stop(); + + virtual sp<MetaData> getFormat(); + + virtual status_t read( + MediaBuffer **buffer, const ReadOptions *options = NULL); + +private: + sp<MetaData> mFormat; + DataSource *mDataSource; + int32_t mTimescale; + SampleTable *mSampleTable; + uint32_t mCurrentSampleIndex; + + bool mIsAVC; + bool mStarted; + + MediaBufferGroup *mGroup; + + MediaBuffer *mBuffer; + size_t mBufferOffset; + size_t mBufferSizeRemaining; + + bool mNeedsNALFraming; + + MPEG4Source(const MPEG4Source &); + MPEG4Source &operator=(const MPEG4Source &); +}; + +static void hexdump(const void *_data, size_t size) { + const uint8_t *data = (const uint8_t *)_data; + size_t offset = 0; + while (offset < size) { + printf("0x%04x ", offset); + + size_t n = size - offset; + if (n > 16) { + n = 16; + } + + for (size_t i = 0; i < 16; ++i) { + if (i == 8) { + printf(" "); + } + + if (offset + i < size) { + printf("%02x ", data[offset + i]); + } else { + printf(" "); + } + } + + printf(" "); + + for (size_t i = 0; i < n; ++i) { + if (isprint(data[offset + i])) { + printf("%c", data[offset + i]); + } else { + printf("."); + } + } + + printf("\n"); + + offset += 16; + } +} + +static const char *const FourCC2MIME(uint32_t fourcc) { + switch (fourcc) { + case FOURCC('m', 'p', '4', 'a'): + return "audio/mp4a-latm"; + + case FOURCC('s', 'a', 'm', 'r'): + return "audio/3gpp"; + + case FOURCC('m', 'p', '4', 'v'): + return "video/mp4v-es"; + + case FOURCC('s', '2', '6', '3'): + return "video/3gpp"; + + case FOURCC('a', 'v', 'c', '1'): + return "video/avc"; + + default: + assert(!"should not be here."); + return NULL; + } +} + +MPEG4Extractor::MPEG4Extractor(DataSource *source) + : mDataSource(source), + mHaveMetadata(false), + mFirstTrack(NULL), + mLastTrack(NULL) { +} + +MPEG4Extractor::~MPEG4Extractor() { + Track *track = mFirstTrack; + while (track) { + Track *next = track->next; + + delete track->sampleTable; + track->sampleTable = NULL; + + delete track; + track = next; + } + mFirstTrack = mLastTrack = NULL; + + delete mDataSource; + mDataSource = NULL; +} + +status_t MPEG4Extractor::countTracks(int *num_tracks) { + status_t err; + if ((err = readMetaData()) != OK) { + return err; + } + + *num_tracks = 0; + Track *track = mFirstTrack; + while (track) { + ++*num_tracks; + track = track->next; + } + + return OK; +} + +sp<MetaData> MPEG4Extractor::getTrackMetaData(int index) { + if (index < 0) { + return NULL; + } + + status_t err; + if ((err = readMetaData()) != OK) { + return NULL; + } + + Track *track = mFirstTrack; + while (index > 0) { + if (track == NULL) { + return NULL; + } + + track = track->next; + --index; + } + + return track->meta; +} + +status_t MPEG4Extractor::readMetaData() { + if (mHaveMetadata) { + return OK; + } + + off_t offset = 0; + status_t err; + while ((err = parseChunk(&offset, 0)) == OK) { + } + + if (mHaveMetadata) { + return OK; + } + + return err; +} + +static void MakeFourCCString(uint32_t x, char *s) { + s[0] = x >> 24; + s[1] = (x >> 16) & 0xff; + s[2] = (x >> 8) & 0xff; + s[3] = x & 0xff; + s[4] = '\0'; +} + +status_t MPEG4Extractor::parseChunk(off_t *offset, int depth) { + uint32_t hdr[2]; + if (mDataSource->read_at(*offset, hdr, 8) < 8) { + return ERROR_IO; + } + uint64_t chunk_size = ntohl(hdr[0]); + uint32_t chunk_type = ntohl(hdr[1]); + off_t data_offset = *offset + 8; + + if (chunk_size == 1) { + if (mDataSource->read_at(*offset + 8, &chunk_size, 8) < 8) { + return ERROR_IO; + } + chunk_size = ntoh64(chunk_size); + data_offset += 8; + } + + char chunk[5]; + MakeFourCCString(chunk_type, chunk); + +#if 0 + static const char kWhitespace[] = " "; + const char *indent = &kWhitespace[sizeof(kWhitespace) - 1 - 2 * depth]; + printf("%sfound chunk '%s' of size %lld\n", indent, chunk, chunk_size); + + char buffer[256]; + if (chunk_size <= sizeof(buffer)) { + if (mDataSource->read_at(*offset, buffer, chunk_size) < chunk_size) { + return ERROR_IO; + } + + hexdump(buffer, chunk_size); + } +#endif + + off_t chunk_data_size = *offset + chunk_size - data_offset; + + switch(chunk_type) { + case FOURCC('m', 'o', 'o', 'v'): + case FOURCC('t', 'r', 'a', 'k'): + case FOURCC('m', 'd', 'i', 'a'): + case FOURCC('m', 'i', 'n', 'f'): + case FOURCC('d', 'i', 'n', 'f'): + case FOURCC('s', 't', 'b', 'l'): + case FOURCC('m', 'v', 'e', 'x'): + case FOURCC('m', 'o', 'o', 'f'): + case FOURCC('t', 'r', 'a', 'f'): + case FOURCC('m', 'f', 'r', 'a'): + case FOURCC('s', 'k', 'i' ,'p'): + { + off_t stop_offset = *offset + chunk_size; + *offset = data_offset; + while (*offset < stop_offset) { + status_t err = parseChunk(offset, depth + 1); + if (err != OK) { + return err; + } + } + assert(*offset == stop_offset); + + if (chunk_type == FOURCC('m', 'o', 'o', 'v')) { + mHaveMetadata = true; + + return UNKNOWN_ERROR; // Return a dummy error. + } + break; + } + + case FOURCC('t', 'k', 'h', 'd'): + { + assert(chunk_data_size >= 4); + + uint8_t version; + if (mDataSource->read_at(data_offset, &version, 1) < 1) { + return ERROR_IO; + } + + uint64_t ctime, mtime, duration; + int32_t id; + uint32_t width, height; + + if (version == 1) { + if (chunk_data_size != 36 + 60) { + return ERROR_MALFORMED; + } + + uint8_t buffer[36 + 60]; + if (mDataSource->read_at( + data_offset, buffer, sizeof(buffer)) < (ssize_t)sizeof(buffer)) { + return ERROR_IO; + } + + ctime = U64_AT(&buffer[4]); + mtime = U64_AT(&buffer[12]); + id = U32_AT(&buffer[20]); + duration = U64_AT(&buffer[28]); + width = U32_AT(&buffer[88]); + height = U32_AT(&buffer[92]); + } else if (version == 0) { + if (chunk_data_size != 24 + 60) { + return ERROR_MALFORMED; + } + + uint8_t buffer[24 + 60]; + if (mDataSource->read_at( + data_offset, buffer, sizeof(buffer)) < (ssize_t)sizeof(buffer)) { + return ERROR_IO; + } + ctime = U32_AT(&buffer[4]); + mtime = U32_AT(&buffer[8]); + id = U32_AT(&buffer[12]); + duration = U32_AT(&buffer[20]); + width = U32_AT(&buffer[76]); + height = U32_AT(&buffer[80]); + } + + Track *track = new Track; + track->next = NULL; + if (mLastTrack) { + mLastTrack->next = track; + } else { + mFirstTrack = track; + } + mLastTrack = track; + + track->meta = new MetaData; + track->timescale = 0; + track->sampleTable = new SampleTable(mDataSource); + track->meta->setCString(kKeyMIMEType, "application/octet-stream"); + + *offset += chunk_size; + break; + } + + case FOURCC('m', 'd', 'h', 'd'): + { + if (chunk_data_size < 4) { + return ERROR_MALFORMED; + } + + uint8_t version; + if (mDataSource->read_at( + data_offset, &version, sizeof(version)) + < (ssize_t)sizeof(version)) { + return ERROR_IO; + } + + off_t timescale_offset; + + if (version == 1) { + timescale_offset = data_offset + 4 + 16; + } else if (version == 0) { + timescale_offset = data_offset + 4 + 8; + } else { + return ERROR_IO; + } + + uint32_t timescale; + if (mDataSource->read_at( + timescale_offset, ×cale, sizeof(timescale)) + < (ssize_t)sizeof(timescale)) { + return ERROR_IO; + } + + mLastTrack->timescale = ntohl(timescale); + mLastTrack->meta->setInt32(kKeyTimeScale, mLastTrack->timescale); + + int64_t duration; + if (version == 1) { + if (mDataSource->read_at( + timescale_offset + 4, &duration, sizeof(duration)) + < (ssize_t)sizeof(duration)) { + return ERROR_IO; + } + duration = ntoh64(duration); + } else { + int32_t duration32; + if (mDataSource->read_at( + timescale_offset + 4, &duration32, sizeof(duration32)) + < (ssize_t)sizeof(duration32)) { + return ERROR_IO; + } + duration = ntohl(duration32); + } + mLastTrack->meta->setInt32(kKeyDuration, duration); + + *offset += chunk_size; + break; + } + + case FOURCC('h', 'd', 'l', 'r'): + { + if (chunk_data_size < 25) { + return ERROR_MALFORMED; + } + + uint8_t buffer[24]; + if (mDataSource->read_at(data_offset, buffer, 24) < 24) { + return ERROR_IO; + } + + if (U32_AT(buffer) != 0) { + // Should be version 0, flags 0. + return ERROR_MALFORMED; + } + + if (U32_AT(&buffer[4]) != 0) { + // pre_defined should be 0. + return ERROR_MALFORMED; + } + + mHandlerType = U32_AT(&buffer[8]); + *offset += chunk_size; + break; + } + + case FOURCC('s', 't', 's', 'd'): + { + if (chunk_data_size < 8) { + return ERROR_MALFORMED; + } + + uint8_t buffer[8]; + assert(chunk_data_size >= (off_t)sizeof(buffer)); + if (mDataSource->read_at( + data_offset, buffer, 8) < 8) { + return ERROR_IO; + } + + if (U32_AT(buffer) != 0) { + // Should be version 0, flags 0. + return ERROR_MALFORMED; + } + + uint32_t entry_count = U32_AT(&buffer[4]); + + if (entry_count > 1) { + // For now we only support a single type of media per track. + return ERROR_UNSUPPORTED; + } + + off_t stop_offset = *offset + chunk_size; + *offset = data_offset + 8; + for (uint32_t i = 0; i < entry_count; ++i) { + status_t err = parseChunk(offset, depth + 1); + if (err != OK) { + return err; + } + } + assert(*offset == stop_offset); + break; + } + + case FOURCC('m', 'p', '4', 'a'): + case FOURCC('s', 'a', 'm', 'r'): + { + if (mHandlerType != FOURCC('s', 'o', 'u', 'n')) { + return ERROR_MALFORMED; + } + + uint8_t buffer[8 + 20]; + if (chunk_data_size < (ssize_t)sizeof(buffer)) { + // Basic AudioSampleEntry size. + return ERROR_MALFORMED; + } + + if (mDataSource->read_at( + data_offset, buffer, sizeof(buffer)) < (ssize_t)sizeof(buffer)) { + return ERROR_IO; + } + + uint16_t data_ref_index = U16_AT(&buffer[6]); + uint16_t num_channels = U16_AT(&buffer[16]); + + if (!strcasecmp("audio/3gpp", FourCC2MIME(chunk_type))) { + // AMR audio is always mono. + num_channels = 1; + } + + uint16_t sample_size = U16_AT(&buffer[18]); + uint32_t sample_rate = U32_AT(&buffer[24]) >> 16; + + printf("*** coding='%s' %d channels, size %d, rate %d\n", + chunk, num_channels, sample_size, sample_rate); + + mLastTrack->meta->setCString(kKeyMIMEType, FourCC2MIME(chunk_type)); + mLastTrack->meta->setInt32(kKeyChannelCount, num_channels); + mLastTrack->meta->setInt32(kKeySampleRate, sample_rate); + + off_t stop_offset = *offset + chunk_size; + *offset = data_offset + sizeof(buffer); + while (*offset < stop_offset) { + status_t err = parseChunk(offset, depth + 1); + if (err != OK) { + return err; + } + } + assert(*offset == stop_offset); + break; + } + + case FOURCC('m', 'p', '4', 'v'): + case FOURCC('s', '2', '6', '3'): + case FOURCC('a', 'v', 'c', '1'): + { + if (mHandlerType != FOURCC('v', 'i', 'd', 'e')) { + return ERROR_MALFORMED; + } + + uint8_t buffer[78]; + if (chunk_data_size < (ssize_t)sizeof(buffer)) { + // Basic VideoSampleEntry size. + return ERROR_MALFORMED; + } + + if (mDataSource->read_at( + data_offset, buffer, sizeof(buffer)) < (ssize_t)sizeof(buffer)) { + return ERROR_IO; + } + + uint16_t data_ref_index = U16_AT(&buffer[6]); + uint16_t width = U16_AT(&buffer[6 + 18]); + uint16_t height = U16_AT(&buffer[6 + 20]); + + printf("*** coding='%s' width=%d height=%d\n", + chunk, width, height); + + mLastTrack->meta->setCString(kKeyMIMEType, FourCC2MIME(chunk_type)); + mLastTrack->meta->setInt32(kKeyWidth, width); + mLastTrack->meta->setInt32(kKeyHeight, height); + + off_t stop_offset = *offset + chunk_size; + *offset = data_offset + sizeof(buffer); + while (*offset < stop_offset) { + status_t err = parseChunk(offset, depth + 1); + if (err != OK) { + return err; + } + } + assert(*offset == stop_offset); + break; + } + + case FOURCC('s', 't', 'c', 'o'): + case FOURCC('c', 'o', '6', '4'): + { + status_t err = + mLastTrack->sampleTable->setChunkOffsetParams( + chunk_type, data_offset, chunk_data_size); + + if (err != OK) { + return err; + } + + *offset += chunk_size; + break; + } + + case FOURCC('s', 't', 's', 'c'): + { + status_t err = + mLastTrack->sampleTable->setSampleToChunkParams( + data_offset, chunk_data_size); + + if (err != OK) { + return err; + } + + *offset += chunk_size; + break; + } + + case FOURCC('s', 't', 's', 'z'): + case FOURCC('s', 't', 'z', '2'): + { + status_t err = + mLastTrack->sampleTable->setSampleSizeParams( + chunk_type, data_offset, chunk_data_size); + + if (err != OK) { + return err; + } + + *offset += chunk_size; + break; + } + + case FOURCC('s', 't', 't', 's'): + { + status_t err = + mLastTrack->sampleTable->setTimeToSampleParams( + data_offset, chunk_data_size); + + if (err != OK) { + return err; + } + + *offset += chunk_size; + break; + } + + case FOURCC('s', 't', 's', 's'): + { + status_t err = + mLastTrack->sampleTable->setSyncSampleParams( + data_offset, chunk_data_size); + + if (err != OK) { + return err; + } + + *offset += chunk_size; + break; + } + + case FOURCC('e', 's', 'd', 's'): + { + if (chunk_data_size < 4) { + return ERROR_MALFORMED; + } + + uint8_t buffer[256]; + if (chunk_data_size > (off_t)sizeof(buffer)) { + return ERROR_BUFFER_TOO_SMALL; + } + + if (mDataSource->read_at( + data_offset, buffer, chunk_data_size) < chunk_data_size) { + return ERROR_IO; + } + + if (U32_AT(buffer) != 0) { + // Should be version 0, flags 0. + return ERROR_MALFORMED; + } + + mLastTrack->meta->setData( + kKeyESDS, kTypeESDS, &buffer[4], chunk_data_size - 4); + + *offset += chunk_size; + break; + } + + case FOURCC('a', 'v', 'c', 'C'): + { + char buffer[256]; + if (chunk_data_size > (off_t)sizeof(buffer)) { + return ERROR_BUFFER_TOO_SMALL; + } + + if (mDataSource->read_at( + data_offset, buffer, chunk_data_size) < chunk_data_size) { + return ERROR_IO; + } + + mLastTrack->meta->setData( + kKeyAVCC, kTypeAVCC, buffer, chunk_data_size); + + *offset += chunk_size; + break; + } + + default: + { + *offset += chunk_size; + break; + } + } + + return OK; +} + +status_t MPEG4Extractor::getTrack(int index, MediaSource **source) { + *source = NULL; + + if (index < 0) { + return ERROR_OUT_OF_RANGE; + } + + status_t err; + if ((err = readMetaData()) != OK) { + return err; + } + + Track *track = mFirstTrack; + while (index > 0) { + if (track == NULL) { + return ERROR_OUT_OF_RANGE; + } + + track = track->next; + --index; + } + + *source = new MPEG4Source( + track->meta, mDataSource, track->sampleTable); + + return OK; +} + +//////////////////////////////////////////////////////////////////////////////// + +MPEG4Source::MPEG4Source( + const sp<MetaData> &format, + DataSource *dataSource, SampleTable *sampleTable) + : mFormat(format), + mDataSource(dataSource), + mTimescale(0), + mSampleTable(sampleTable), + mCurrentSampleIndex(0), + mIsAVC(false), + mStarted(false), + mGroup(NULL), + mBuffer(NULL), + mBufferOffset(0), + mBufferSizeRemaining(0), + mNeedsNALFraming(false) { + const char *mime; + bool success = mFormat->findCString(kKeyMIMEType, &mime); + assert(success); + + success = mFormat->findInt32(kKeyTimeScale, &mTimescale); + assert(success); + + mIsAVC = !strcasecmp(mime, "video/avc"); +} + +MPEG4Source::~MPEG4Source() { + if (mStarted) { + stop(); + } +} + +status_t MPEG4Source::start(MetaData *params) { + assert(!mStarted); + + int32_t val; + if (mIsAVC && params && params->findInt32(kKeyNeedsNALFraming, &val) + && val != 0) { + mNeedsNALFraming = true; + } else { + mNeedsNALFraming = false; + } + + mGroup = new MediaBufferGroup; + + size_t max_size; + status_t err = mSampleTable->getMaxSampleSize(&max_size); + assert(err == OK); + + // Add padding for de-framing of AVC content just in case. + mGroup->add_buffer(new MediaBuffer(max_size + 2)); + + mStarted = true; + + return OK; +} + +status_t MPEG4Source::stop() { + assert(mStarted); + + if (mBuffer != NULL) { + mBuffer->release(); + mBuffer = NULL; + } + + delete mGroup; + mGroup = NULL; + + mStarted = false; + mCurrentSampleIndex = 0; + + return OK; +} + +sp<MetaData> MPEG4Source::getFormat() { + return mFormat; +} + +status_t MPEG4Source::read( + MediaBuffer **out, const ReadOptions *options) { + assert(mStarted); + + *out = NULL; + + int64_t seekTimeUs; + if (options && options->getSeekTo(&seekTimeUs)) { + uint32_t sampleIndex; + status_t err = mSampleTable->findClosestSample( + seekTimeUs * mTimescale / 1000000, + &sampleIndex, SampleTable::kSyncSample_Flag); + + if (err != OK) { + return err; + } + + mCurrentSampleIndex = sampleIndex; + if (mBuffer != NULL) { + mBuffer->release(); + mBuffer = NULL; + } + + // fall through + } + + if (mBuffer == NULL) { + off_t offset; + size_t size; + status_t err = mSampleTable->getSampleOffsetAndSize( + mCurrentSampleIndex, &offset, &size); + + if (err != OK) { + return err; + } + + uint32_t dts; + err = mSampleTable->getDecodingTime(mCurrentSampleIndex, &dts); + + if (err != OK) { + return err; + } + + err = mGroup->acquire_buffer(&mBuffer); + if (err != OK) { + assert(mBuffer == NULL); + return err; + } + + assert(mBuffer->size() + 2 >= size); + + ssize_t num_bytes_read = + mDataSource->read_at(offset, (uint8_t *)mBuffer->data() + 2, size); + + if (num_bytes_read < (ssize_t)size) { + mBuffer->release(); + mBuffer = NULL; + + return err; + } + + mBuffer->set_range(2, size); + mBuffer->meta_data()->clear(); + mBuffer->meta_data()->setInt32(kKeyTimeUnits, dts); + mBuffer->meta_data()->setInt32(kKeyTimeScale, mTimescale); + + ++mCurrentSampleIndex; + + mBufferOffset = 2; + mBufferSizeRemaining = size; + } + + if (!mIsAVC) { + *out = mBuffer; + mBuffer = NULL; + + return OK; + } + + uint8_t *data = (uint8_t *)mBuffer->data() + mBufferOffset; + assert(mBufferSizeRemaining >= 2); + + size_t nal_length = (data[0] << 8) | data[1]; + assert(mBufferSizeRemaining >= 2 + nal_length); + + if (mNeedsNALFraming) { + // Insert marker. + data[-2] = data[-1] = data[0] = 0; + data[1] = 1; + + mBuffer->set_range(mBufferOffset - 2, nal_length + 4); + } else { + mBuffer->set_range(mBufferOffset + 2, nal_length); + } + + mBufferOffset += nal_length + 2; + mBufferSizeRemaining -= nal_length + 2; + + if (mBufferSizeRemaining > 0) { + *out = mBuffer->clone(); + } else { + *out = mBuffer; + mBuffer = NULL; + } + + return OK; +} + +bool SniffMPEG4(DataSource *source, String8 *mimeType, float *confidence) { + uint8_t header[8]; + + ssize_t n = source->read_at(4, header, sizeof(header)); + if (n < (ssize_t)sizeof(header)) { + return false; + } + + if (!memcmp(header, "ftyp3gp", 7) || !memcmp(header, "ftypmp42", 8) + || !memcmp(header, "ftypisom", 8) || !memcmp(header, "ftypM4V ", 8)) { + *mimeType = "video/mp4"; + *confidence = 0.1; + + return true; + } + + return false; +} + +} // namespace android + diff --git a/media/libstagefright/MPEG4Writer.cpp b/media/libstagefright/MPEG4Writer.cpp new file mode 100644 index 0000000..6bdf282 --- /dev/null +++ b/media/libstagefright/MPEG4Writer.cpp @@ -0,0 +1,641 @@ +/* + * Copyright (C) 2009 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. + */ + +#include <arpa/inet.h> + +#undef NDEBUG +#include <assert.h> + +#include <ctype.h> +#include <pthread.h> + +#include <media/stagefright/MPEG4Writer.h> +#include <media/stagefright/MediaBuffer.h> +#include <media/stagefright/MetaData.h> +#include <media/stagefright/MediaSource.h> +#include <media/stagefright/Utils.h> + +namespace android { + +class MPEG4Writer::Track { +public: + Track(MPEG4Writer *owner, const sp<MetaData> &meta, MediaSource *source); + ~Track(); + + void start(); + void stop(); + + int64_t getDuration() const; + void writeTrackHeader(int32_t trackID); + +private: + MPEG4Writer *mOwner; + sp<MetaData> mMeta; + MediaSource *mSource; + volatile bool mDone; + + pthread_t mThread; + + struct SampleInfo { + size_t size; + off_t offset; + int64_t timestamp; + }; + List<SampleInfo> mSampleInfos; + + void *mCodecSpecificData; + size_t mCodecSpecificDataSize; + + static void *ThreadWrapper(void *me); + void threadEntry(); + + Track(const Track &); + Track &operator=(const Track &); +}; + +MPEG4Writer::MPEG4Writer(const char *filename) + : mFile(fopen(filename, "wb")), + mOffset(0), + mMdatOffset(0) { + assert(mFile != NULL); +} + +MPEG4Writer::~MPEG4Writer() { + stop(); + + for (List<Track *>::iterator it = mTracks.begin(); + it != mTracks.end(); ++it) { + delete *it; + } + mTracks.clear(); +} + +void MPEG4Writer::addSource(const sp<MetaData> &meta, MediaSource *source) { + Track *track = new Track(this, meta, source); + mTracks.push_back(track); +} + +void MPEG4Writer::start() { + if (mFile == NULL) { + return; + } + + beginBox("ftyp"); + writeFourcc("isom"); + writeInt32(0); + writeFourcc("isom"); + endBox(); + + mMdatOffset = mOffset; + write("\x00\x00\x00\x01mdat????????", 16); + + for (List<Track *>::iterator it = mTracks.begin(); + it != mTracks.end(); ++it) { + (*it)->start(); + } +} + +void MPEG4Writer::stop() { + if (mFile == NULL) { + return; + } + + int64_t max_duration = 0; + for (List<Track *>::iterator it = mTracks.begin(); + it != mTracks.end(); ++it) { + (*it)->stop(); + + int64_t duration = (*it)->getDuration(); + if (duration > max_duration) { + max_duration = duration; + } + } + + // Fix up the size of the 'mdat' chunk. + fseek(mFile, mMdatOffset + 8, SEEK_SET); + int64_t size = mOffset - mMdatOffset; + size = hton64(size); + fwrite(&size, 1, 8, mFile); + fseek(mFile, mOffset, SEEK_SET); + + time_t now = time(NULL); + + beginBox("moov"); + + beginBox("mvhd"); + writeInt32(0); // version=0, flags=0 + writeInt32(now); // creation time + writeInt32(now); // modification time + writeInt32(1000); // timescale + writeInt32(max_duration); + writeInt32(0x10000); // rate + writeInt16(0x100); // volume + writeInt16(0); // reserved + writeInt32(0); // reserved + writeInt32(0); // reserved + writeInt32(0x10000); // matrix + writeInt32(0); + writeInt32(0); + writeInt32(0); + writeInt32(0x10000); + writeInt32(0); + writeInt32(0); + writeInt32(0); + writeInt32(0x40000000); + writeInt32(0); // predefined + writeInt32(0); // predefined + writeInt32(0); // predefined + writeInt32(0); // predefined + writeInt32(0); // predefined + writeInt32(0); // predefined + writeInt32(mTracks.size() + 1); // nextTrackID + endBox(); // mvhd + + int32_t id = 1; + for (List<Track *>::iterator it = mTracks.begin(); + it != mTracks.end(); ++it, ++id) { + (*it)->writeTrackHeader(id); + } + endBox(); // moov + + assert(mBoxes.empty()); + + fclose(mFile); + mFile = NULL; +} + +off_t MPEG4Writer::addSample(MediaBuffer *buffer) { + Mutex::Autolock autoLock(mLock); + + off_t old_offset = mOffset; + + fwrite((const uint8_t *)buffer->data() + buffer->range_offset(), + 1, buffer->range_length(), mFile); + + mOffset += buffer->range_length(); + + return old_offset; +} + +void MPEG4Writer::beginBox(const char *fourcc) { + assert(strlen(fourcc) == 4); + + mBoxes.push_back(mOffset); + + writeInt32(0); + writeFourcc(fourcc); +} + +void MPEG4Writer::endBox() { + assert(!mBoxes.empty()); + + off_t offset = *--mBoxes.end(); + mBoxes.erase(--mBoxes.end()); + + fseek(mFile, offset, SEEK_SET); + writeInt32(mOffset - offset); + mOffset -= 4; + fseek(mFile, mOffset, SEEK_SET); +} + +void MPEG4Writer::writeInt8(int8_t x) { + fwrite(&x, 1, 1, mFile); + ++mOffset; +} + +void MPEG4Writer::writeInt16(int16_t x) { + x = htons(x); + fwrite(&x, 1, 2, mFile); + mOffset += 2; +} + +void MPEG4Writer::writeInt32(int32_t x) { + x = htonl(x); + fwrite(&x, 1, 4, mFile); + mOffset += 4; +} + +void MPEG4Writer::writeInt64(int64_t x) { + x = hton64(x); + fwrite(&x, 1, 8, mFile); + mOffset += 8; +} + +void MPEG4Writer::writeCString(const char *s) { + size_t n = strlen(s); + + fwrite(s, 1, n + 1, mFile); + mOffset += n + 1; +} + +void MPEG4Writer::writeFourcc(const char *s) { + assert(strlen(s) == 4); + fwrite(s, 1, 4, mFile); + mOffset += 4; +} + +void MPEG4Writer::write(const void *data, size_t size) { + fwrite(data, 1, size, mFile); + mOffset += size; +} + +//////////////////////////////////////////////////////////////////////////////// + +MPEG4Writer::Track::Track( + MPEG4Writer *owner, const sp<MetaData> &meta, MediaSource *source) + : mOwner(owner), + mMeta(meta), + mSource(source), + mDone(false), + mCodecSpecificData(NULL), + mCodecSpecificDataSize(0) { +} + +MPEG4Writer::Track::~Track() { + stop(); + + if (mCodecSpecificData != NULL) { + free(mCodecSpecificData); + mCodecSpecificData = NULL; + } +} + +void MPEG4Writer::Track::start() { + mSource->start(); + + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); + + mDone = false; + + int err = pthread_create(&mThread, &attr, ThreadWrapper, this); + assert(err == 0); + + pthread_attr_destroy(&attr); +} + +void MPEG4Writer::Track::stop() { + if (mDone) { + return; + } + + mDone = true; + + void *dummy; + pthread_join(mThread, &dummy); + + mSource->stop(); +} + +// static +void *MPEG4Writer::Track::ThreadWrapper(void *me) { + Track *track = static_cast<Track *>(me); + + track->threadEntry(); + + return NULL; +} + +void MPEG4Writer::Track::threadEntry() { + bool is_mpeg4 = false; + sp<MetaData> meta = mSource->getFormat(); + const char *mime; + meta->findCString(kKeyMIMEType, &mime); + is_mpeg4 = !strcasecmp(mime, "video/mp4v-es"); + + MediaBuffer *buffer; + while (!mDone && mSource->read(&buffer) == OK) { + if (buffer->range_length() == 0) { + buffer->release(); + buffer = NULL; + + continue; + } + + if (mCodecSpecificData == NULL && is_mpeg4) { + const uint8_t *data = + (const uint8_t *)buffer->data() + buffer->range_offset(); + + const size_t size = buffer->range_length(); + + size_t offset = 0; + while (offset + 3 < size) { + if (data[offset] == 0x00 && data[offset + 1] == 0x00 + && data[offset + 2] == 0x01 && data[offset + 3] == 0xb6) { + break; + } + + ++offset; + } + + assert(offset + 3 < size); + + mCodecSpecificDataSize = offset; + mCodecSpecificData = malloc(offset); + memcpy(mCodecSpecificData, data, offset); + + buffer->set_range(buffer->range_offset() + offset, size - offset); + } + + off_t offset = mOwner->addSample(buffer); + + SampleInfo info; + info.size = buffer->range_length(); + info.offset = offset; + + int32_t units, scale; + bool success = + buffer->meta_data()->findInt32(kKeyTimeUnits, &units); + assert(success); + success = + buffer->meta_data()->findInt32(kKeyTimeScale, &scale); + assert(success); + + info.timestamp = (int64_t)units * 1000 / scale; + + mSampleInfos.push_back(info); + + buffer->release(); + buffer = NULL; + } +} + +int64_t MPEG4Writer::Track::getDuration() const { + return 10000; // XXX +} + +void MPEG4Writer::Track::writeTrackHeader(int32_t trackID) { + const char *mime; + bool success = mMeta->findCString(kKeyMIMEType, &mime); + assert(success); + + bool is_audio = !strncasecmp(mime, "audio/", 6); + + time_t now = time(NULL); + + mOwner->beginBox("trak"); + + mOwner->beginBox("tkhd"); + mOwner->writeInt32(0); // version=0, flags=0 + mOwner->writeInt32(now); // creation time + mOwner->writeInt32(now); // modification time + mOwner->writeInt32(trackID); + mOwner->writeInt32(0); // reserved + mOwner->writeInt32(getDuration()); + mOwner->writeInt32(0); // reserved + mOwner->writeInt32(0); // reserved + mOwner->writeInt16(0); // layer + mOwner->writeInt16(0); // alternate group + mOwner->writeInt16(is_audio ? 0x100 : 0); // volume + mOwner->writeInt16(0); // reserved + + mOwner->writeInt32(0x10000); // matrix + mOwner->writeInt32(0); + mOwner->writeInt32(0); + mOwner->writeInt32(0); + mOwner->writeInt32(0x10000); + mOwner->writeInt32(0); + mOwner->writeInt32(0); + mOwner->writeInt32(0); + mOwner->writeInt32(0x40000000); + + if (is_audio) { + mOwner->writeInt32(0); + mOwner->writeInt32(0); + } else { + int32_t width, height; + bool success = mMeta->findInt32(kKeyWidth, &width); + success = success && mMeta->findInt32(kKeyHeight, &height); + assert(success); + + mOwner->writeInt32(width); + mOwner->writeInt32(height); + } + mOwner->endBox(); // tkhd + + mOwner->beginBox("mdia"); + + mOwner->beginBox("mdhd"); + mOwner->writeInt32(0); // version=0, flags=0 + mOwner->writeInt32(now); // creation time + mOwner->writeInt32(now); // modification time + mOwner->writeInt32(1000); // timescale + mOwner->writeInt32(getDuration()); + mOwner->writeInt16(0); // language code XXX + mOwner->writeInt16(0); // predefined + mOwner->endBox(); + + mOwner->beginBox("hdlr"); + mOwner->writeInt32(0); // version=0, flags=0 + mOwner->writeInt32(0); // predefined + mOwner->writeFourcc(is_audio ? "soun" : "vide"); + mOwner->writeInt32(0); // reserved + mOwner->writeInt32(0); // reserved + mOwner->writeInt32(0); // reserved + mOwner->writeCString(""); // name + mOwner->endBox(); + + mOwner->beginBox("minf"); + + mOwner->beginBox("dinf"); + mOwner->beginBox("dref"); + mOwner->writeInt32(0); // version=0, flags=0 + mOwner->writeInt32(1); + mOwner->beginBox("url "); + mOwner->writeInt32(1); // version=0, flags=1 + mOwner->endBox(); // url + mOwner->endBox(); // dref + mOwner->endBox(); // dinf + + if (is_audio) { + mOwner->beginBox("smhd"); + mOwner->writeInt32(0); // version=0, flags=0 + mOwner->writeInt16(0); // balance + mOwner->writeInt16(0); // reserved + mOwner->endBox(); + } else { + mOwner->beginBox("vmhd"); + mOwner->writeInt32(0x00000001); // version=0, flags=1 + mOwner->writeInt16(0); // graphics mode + mOwner->writeInt16(0); // opcolor + mOwner->writeInt16(0); + mOwner->writeInt16(0); + mOwner->endBox(); + } + mOwner->endBox(); // minf + + mOwner->beginBox("stbl"); + + mOwner->beginBox("stsd"); + mOwner->writeInt32(0); // version=0, flags=0 + mOwner->writeInt32(1); // entry count + if (is_audio) { + mOwner->beginBox("xxxx"); // audio format XXX + mOwner->writeInt32(0); // reserved + mOwner->writeInt16(0); // reserved + mOwner->writeInt16(0); // data ref index + mOwner->writeInt32(0); // reserved + mOwner->writeInt32(0); // reserved + mOwner->writeInt16(2); // channel count + mOwner->writeInt16(16); // sample size + mOwner->writeInt16(0); // predefined + mOwner->writeInt16(0); // reserved + + int32_t samplerate; + bool success = mMeta->findInt32(kKeySampleRate, &samplerate); + assert(success); + + mOwner->writeInt32(samplerate << 16); + mOwner->endBox(); + } else { + if (!strcasecmp("video/mp4v-es", mime)) { + mOwner->beginBox("mp4v"); + } else if (!strcasecmp("video/3gpp", mime)) { + mOwner->beginBox("s263"); + } else { + assert(!"should not be here, unknown mime type."); + } + + mOwner->writeInt32(0); // reserved + mOwner->writeInt16(0); // reserved + mOwner->writeInt16(0); // data ref index + mOwner->writeInt16(0); // predefined + mOwner->writeInt16(0); // reserved + mOwner->writeInt32(0); // predefined + mOwner->writeInt32(0); // predefined + mOwner->writeInt32(0); // predefined + + int32_t width, height; + bool success = mMeta->findInt32(kKeyWidth, &width); + success = success && mMeta->findInt32(kKeyHeight, &height); + assert(success); + + mOwner->writeInt16(width); + mOwner->writeInt16(height); + mOwner->writeInt32(0x480000); // horiz resolution + mOwner->writeInt32(0x480000); // vert resolution + mOwner->writeInt32(0); // reserved + mOwner->writeInt16(1); // frame count + mOwner->write(" ", 32); + mOwner->writeInt16(0x18); // depth + mOwner->writeInt16(-1); // predefined + + assert(23 + mCodecSpecificDataSize < 128); + + if (!strcasecmp("video/mp4v-es", mime)) { + mOwner->beginBox("esds"); + + mOwner->writeInt32(0); // version=0, flags=0 + + mOwner->writeInt8(0x03); // ES_DescrTag + mOwner->writeInt8(23 + mCodecSpecificDataSize); + mOwner->writeInt16(0x0000); // ES_ID + mOwner->writeInt8(0x1f); + + mOwner->writeInt8(0x04); // DecoderConfigDescrTag + mOwner->writeInt8(15 + mCodecSpecificDataSize); + mOwner->writeInt8(0x20); // objectTypeIndication ISO/IEC 14492-2 + mOwner->writeInt8(0x11); // streamType VisualStream + + static const uint8_t kData[] = { + 0x01, 0x77, 0x00, + 0x00, 0x03, 0xe8, 0x00, + 0x00, 0x03, 0xe8, 0x00 + }; + mOwner->write(kData, sizeof(kData)); + + mOwner->writeInt8(0x05); // DecoderSpecificInfoTag + + mOwner->writeInt8(mCodecSpecificDataSize); + mOwner->write(mCodecSpecificData, mCodecSpecificDataSize); + + static const uint8_t kData2[] = { + 0x06, // SLConfigDescriptorTag + 0x01, + 0x02 + }; + mOwner->write(kData2, sizeof(kData2)); + + mOwner->endBox(); // esds + } else if (!strcasecmp("video/3gpp", mime)) { + mOwner->beginBox("d263"); + + mOwner->writeInt32(0); // vendor + mOwner->writeInt8(0); // decoder version + mOwner->writeInt8(10); // level: 10 + mOwner->writeInt8(0); // profile: 0 + + mOwner->endBox(); // d263 + } + mOwner->endBox(); // mp4v or s263 + } + mOwner->endBox(); // stsd + + mOwner->beginBox("stts"); + mOwner->writeInt32(0); // version=0, flags=0 + mOwner->writeInt32(mSampleInfos.size() - 1); + + List<SampleInfo>::iterator it = mSampleInfos.begin(); + int64_t last = (*it).timestamp; + ++it; + while (it != mSampleInfos.end()) { + mOwner->writeInt32(1); + mOwner->writeInt32((*it).timestamp - last); + + last = (*it).timestamp; + + ++it; + } + mOwner->endBox(); // stts + + mOwner->beginBox("stsz"); + mOwner->writeInt32(0); // version=0, flags=0 + mOwner->writeInt32(0); // default sample size + mOwner->writeInt32(mSampleInfos.size()); + for (List<SampleInfo>::iterator it = mSampleInfos.begin(); + it != mSampleInfos.end(); ++it) { + mOwner->writeInt32((*it).size); + } + mOwner->endBox(); // stsz + + mOwner->beginBox("stsc"); + mOwner->writeInt32(0); // version=0, flags=0 + mOwner->writeInt32(mSampleInfos.size()); + int32_t n = 1; + for (List<SampleInfo>::iterator it = mSampleInfos.begin(); + it != mSampleInfos.end(); ++it, ++n) { + mOwner->writeInt32(n); + mOwner->writeInt32(1); + mOwner->writeInt32(1); + } + mOwner->endBox(); // stsc + + mOwner->beginBox("co64"); + mOwner->writeInt32(0); // version=0, flags=0 + mOwner->writeInt32(mSampleInfos.size()); + for (List<SampleInfo>::iterator it = mSampleInfos.begin(); + it != mSampleInfos.end(); ++it, ++n) { + mOwner->writeInt64((*it).offset); + } + mOwner->endBox(); // co64 + + mOwner->endBox(); // stbl + mOwner->endBox(); // mdia + mOwner->endBox(); // trak +} + +} // namespace android diff --git a/media/libstagefright/MediaBuffer.cpp b/media/libstagefright/MediaBuffer.cpp new file mode 100644 index 0000000..cd78dbd --- /dev/null +++ b/media/libstagefright/MediaBuffer.cpp @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2009 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_TAG "MediaBuffer" +#include <utils/Log.h> + +#undef NDEBUG +#include <assert.h> + +#include <errno.h> +#include <pthread.h> +#include <stdlib.h> + +#include <media/stagefright/MediaBuffer.h> +#include <media/stagefright/MetaData.h> + +namespace android { + +// XXX make this truly atomic. +static int atomic_add(int *value, int delta) { + int prev_value = *value; + *value += delta; + + return prev_value; +} + +MediaBuffer::MediaBuffer(void *data, size_t size) + : mObserver(NULL), + mNextBuffer(NULL), + mRefCount(0), + mData(data), + mSize(size), + mRangeOffset(0), + mRangeLength(size), + mOwnsData(false), + mMetaData(new MetaData), + mOriginal(NULL) { +} + +MediaBuffer::MediaBuffer(size_t size) + : mObserver(NULL), + mNextBuffer(NULL), + mRefCount(0), + mData(malloc(size)), + mSize(size), + mRangeOffset(0), + mRangeLength(size), + mOwnsData(true), + mMetaData(new MetaData), + mOriginal(NULL) { +} + +void MediaBuffer::release() { + if (mObserver == NULL) { + assert(mRefCount == 0); + delete this; + return; + } + + int prevCount = atomic_add(&mRefCount, -1); + if (prevCount == 1) { + if (mObserver == NULL) { + delete this; + return; + } + + mObserver->signalBufferReturned(this); + } + assert(prevCount > 0); +} + +void MediaBuffer::claim() { + assert(mObserver != NULL); + assert(mRefCount == 1); + + mRefCount = 0; +} + +void MediaBuffer::add_ref() { + atomic_add(&mRefCount, 1); +} + +void *MediaBuffer::data() const { + return mData; +} + +size_t MediaBuffer::size() const { + return mSize; +} + +size_t MediaBuffer::range_offset() const { + return mRangeOffset; +} + +size_t MediaBuffer::range_length() const { + return mRangeLength; +} + +void MediaBuffer::set_range(size_t offset, size_t length) { + if (offset < 0 || offset + length > mSize) { + LOGE("offset = %d, length = %d, mSize = %d", offset, length, mSize); + } + assert(offset >= 0 && offset + length <= mSize); + + mRangeOffset = offset; + mRangeLength = length; +} + +sp<MetaData> MediaBuffer::meta_data() { + return mMetaData; +} + +void MediaBuffer::reset() { + mMetaData->clear(); + set_range(0, mSize); +} + +MediaBuffer::~MediaBuffer() { + assert(mObserver == NULL); + + if (mOwnsData && mData != NULL) { + free(mData); + mData = NULL; + } + + if (mOriginal != NULL) { + mOriginal->release(); + mOriginal = NULL; + } +} + +void MediaBuffer::setObserver(MediaBufferObserver *observer) { + assert(observer == NULL || mObserver == NULL); + mObserver = observer; +} + +void MediaBuffer::setNextBuffer(MediaBuffer *buffer) { + mNextBuffer = buffer; +} + +MediaBuffer *MediaBuffer::nextBuffer() { + return mNextBuffer; +} + +int MediaBuffer::refcount() const { + return mRefCount; +} + +MediaBuffer *MediaBuffer::clone() { + MediaBuffer *buffer = new MediaBuffer(mData, mSize); + buffer->set_range(mRangeOffset, mRangeLength); + buffer->mMetaData = new MetaData(*mMetaData.get()); + + add_ref(); + buffer->mOriginal = this; + + return buffer; +} + +} // namespace android + diff --git a/media/libstagefright/MediaBufferGroup.cpp b/media/libstagefright/MediaBufferGroup.cpp new file mode 100644 index 0000000..aec7722 --- /dev/null +++ b/media/libstagefright/MediaBufferGroup.cpp @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2009 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_TAG "MediaBufferGroup" +#include <utils/Log.h> + +#undef NDEBUG +#include <assert.h> + +#include <media/stagefright/MediaBuffer.h> +#include <media/stagefright/MediaBufferGroup.h> + +namespace android { + +MediaBufferGroup::MediaBufferGroup() + : mFirstBuffer(NULL), + mLastBuffer(NULL) { +} + +MediaBufferGroup::~MediaBufferGroup() { + MediaBuffer *next; + for (MediaBuffer *buffer = mFirstBuffer; buffer != NULL; + buffer = next) { + next = buffer->nextBuffer(); + + assert(buffer->refcount() == 0); + + buffer->setObserver(NULL); + buffer->release(); + } +} + +void MediaBufferGroup::add_buffer(MediaBuffer *buffer) { + Mutex::Autolock autoLock(mLock); + + buffer->setObserver(this); + + if (mLastBuffer) { + mLastBuffer->setNextBuffer(buffer); + } else { + mFirstBuffer = buffer; + } + + mLastBuffer = buffer; +} + +status_t MediaBufferGroup::acquire_buffer(MediaBuffer **out) { + Mutex::Autolock autoLock(mLock); + + for (;;) { + for (MediaBuffer *buffer = mFirstBuffer; + buffer != NULL; buffer = buffer->nextBuffer()) { + if (buffer->refcount() == 0) { + buffer->add_ref(); + buffer->reset(); + + *out = buffer; + goto exit; + } + } + + // All buffers are in use. Block until one of them is returned to us. + mCondition.wait(mLock); + } + +exit: + return OK; +} + +void MediaBufferGroup::signalBufferReturned(MediaBuffer *) { + Mutex::Autolock autoLock(mLock); + mCondition.signal(); +} + +} // namespace android diff --git a/media/libstagefright/MediaExtractor.cpp b/media/libstagefright/MediaExtractor.cpp new file mode 100644 index 0000000..bc66794 --- /dev/null +++ b/media/libstagefright/MediaExtractor.cpp @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2009 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 "MediaExtractor" +#include <utils/Log.h> + +#include <media/stagefright/DataSource.h> +#include <media/stagefright/MP3Extractor.h> +#include <media/stagefright/MPEG4Extractor.h> +#include <media/stagefright/MediaExtractor.h> +#include <utils/String8.h> + +namespace android { + +// static +MediaExtractor *MediaExtractor::Create(DataSource *source, const char *mime) { + String8 tmp; + if (mime == NULL) { + float confidence; + if (!source->sniff(&tmp, &confidence)) { + LOGE("FAILED to autodetect media content."); + + return NULL; + } + + mime = tmp.string(); + LOGI("Autodetected media content as '%s' with confidence %.2f", + mime, confidence); + } + + if (!strcasecmp(mime, "video/mp4") || !strcasecmp(mime, "audio/mp4")) { + return new MPEG4Extractor(source); + } else if (!strcasecmp(mime, "audio/mpeg")) { + return new MP3Extractor(source); + } + + return NULL; +} + +} // namespace android diff --git a/media/libstagefright/MediaPlayerImpl.cpp b/media/libstagefright/MediaPlayerImpl.cpp new file mode 100644 index 0000000..78fcdee --- /dev/null +++ b/media/libstagefright/MediaPlayerImpl.cpp @@ -0,0 +1,693 @@ +/* + * Copyright (C) 2009 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 "MediaPlayerImpl" +#include "utils/Log.h" + +#undef NDEBUG +#include <assert.h> + +#include <OMX_Component.h> + +#include <unistd.h> + +#include <media/stagefright/AudioPlayer.h> +#include <media/stagefright/CachingDataSource.h> +// #include <media/stagefright/CameraSource.h> +#include <media/stagefright/HTTPDataSource.h> +#include <media/stagefright/HTTPStream.h> +#include <media/stagefright/MediaExtractor.h> +#include <media/stagefright/MediaPlayerImpl.h> +#include <media/stagefright/MetaData.h> +#include <media/stagefright/MmapSource.h> +#include <media/stagefright/OMXDecoder.h> +#include <media/stagefright/QComHardwareRenderer.h> +#include <media/stagefright/ShoutcastSource.h> +#include <media/stagefright/SoftwareRenderer.h> +#include <media/stagefright/SurfaceRenderer.h> +#include <media/stagefright/TimeSource.h> +#include <ui/PixelFormat.h> +#include <ui/Surface.h> + +namespace android { + +MediaPlayerImpl::MediaPlayerImpl(const char *uri) + : mInitCheck(NO_INIT), + mExtractor(NULL), + mTimeSource(NULL), + mAudioSource(NULL), + mAudioDecoder(NULL), + mAudioPlayer(NULL), + mVideoSource(NULL), + mVideoDecoder(NULL), + mVideoWidth(0), + mVideoHeight(0), + mVideoPosition(0), + mDuration(0), + mPlaying(false), + mPaused(false), + mRenderer(NULL), + mSeeking(false), + mFrameSize(0), + mUseSoftwareColorConversion(false) { + LOGI("MediaPlayerImpl(%s)", uri); + DataSource::RegisterDefaultSniffers(); + + status_t err = mClient.connect(); + if (err != OK) { + LOGE("Failed to connect to OMXClient."); + return; + } + + if (!strncasecmp("shoutcast://", uri, 12)) { + setAudioSource(makeShoutcastSource(uri)); +#if 0 + } else if (!strncasecmp("camera:", uri, 7)) { + mVideoWidth = 480; + mVideoHeight = 320; + mVideoDecoder = CameraSource::Create(); +#endif + } else { + DataSource *source = NULL; + if (!strncasecmp("file://", uri, 7)) { + source = new MmapSource(uri + 7); + } else if (!strncasecmp("http://", uri, 7)) { + source = new HTTPDataSource(uri); + source = new CachingDataSource(source, 64 * 1024, 10); + } else { + // Assume it's a filename. + source = new MmapSource(uri); + } + + mExtractor = MediaExtractor::Create(source); + + if (mExtractor == NULL) { + return; + } + } + + init(); + + mInitCheck = OK; +} + +MediaPlayerImpl::MediaPlayerImpl(int fd, int64_t offset, int64_t length) + : mInitCheck(NO_INIT), + mExtractor(NULL), + mTimeSource(NULL), + mAudioSource(NULL), + mAudioDecoder(NULL), + mAudioPlayer(NULL), + mVideoSource(NULL), + mVideoDecoder(NULL), + mVideoWidth(0), + mVideoHeight(0), + mVideoPosition(0), + mDuration(0), + mPlaying(false), + mPaused(false), + mRenderer(NULL), + mSeeking(false), + mFrameSize(0), + mUseSoftwareColorConversion(false) { + LOGI("MediaPlayerImpl(%d, %lld, %lld)", fd, offset, length); + DataSource::RegisterDefaultSniffers(); + + status_t err = mClient.connect(); + if (err != OK) { + LOGE("Failed to connect to OMXClient."); + return; + } + + mExtractor = MediaExtractor::Create( + new MmapSource(fd, offset, length)); + + if (mExtractor == NULL) { + return; + } + + init(); + + mInitCheck = OK; +} + +status_t MediaPlayerImpl::initCheck() const { + return mInitCheck; +} + +MediaPlayerImpl::~MediaPlayerImpl() { + stop(); + setSurface(NULL); + + LOGV("Shutting down audio."); + delete mAudioDecoder; + mAudioDecoder = NULL; + + delete mAudioSource; + mAudioSource = NULL; + + LOGV("Shutting down video."); + delete mVideoDecoder; + mVideoDecoder = NULL; + + delete mVideoSource; + mVideoSource = NULL; + + delete mExtractor; + mExtractor = NULL; + + if (mInitCheck == OK) { + mClient.disconnect(); + } + + LOGV("~MediaPlayerImpl done."); +} + +void MediaPlayerImpl::play() { + LOGI("play"); + + if (mPlaying) { + if (mPaused) { + if (mAudioSource != NULL) { + mAudioPlayer->resume(); + } + mPaused = false; + } + return; + } + + mPlaying = true; + + if (mAudioSource != NULL) { + mAudioPlayer = new AudioPlayer(mAudioSink); + mAudioPlayer->setSource(mAudioDecoder); + mAudioPlayer->start(); + mTimeSource = mAudioPlayer; + } else { + mTimeSource = new SystemTimeSource; + } + + if (mVideoDecoder != NULL) { + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); + + pthread_create(&mVideoThread, &attr, VideoWrapper, this); + + pthread_attr_destroy(&attr); + } +} + +void MediaPlayerImpl::pause() { + if (!mPlaying || mPaused) { + return; + } + + if (mAudioSource != NULL) { + mAudioPlayer->pause(); + } + + mPaused = true; +} + +void MediaPlayerImpl::stop() { + if (!mPlaying) { + return; + } + + mPlaying = false; + + if (mVideoDecoder != NULL) { + void *dummy; + pthread_join(mVideoThread, &dummy); + } + + if (mAudioSource != NULL) { + mAudioPlayer->stop(); + + delete mAudioPlayer; + mAudioPlayer = NULL; + } else { + delete mTimeSource; + } + + mTimeSource = NULL; +} + +// static +void *MediaPlayerImpl::VideoWrapper(void *me) { + ((MediaPlayerImpl *)me)->videoEntry(); + + return NULL; +} + +void MediaPlayerImpl::videoEntry() { + bool firstFrame = true; + bool eof = false; + + status_t err = mVideoDecoder->start(); + assert(err == OK); + + while (mPlaying) { + MediaBuffer *buffer; + + MediaSource::ReadOptions options; + bool seeking = false; + + { + Mutex::Autolock autoLock(mLock); + if (mSeeking) { + LOGI("seek-options to %lld", mSeekTimeUs); + options.setSeekTo(mSeekTimeUs); + + mSeeking = false; + seeking = true; + eof = false; + } + } + + if (eof || mPaused) { + usleep(100000); + continue; + } + + status_t err = mVideoDecoder->read(&buffer, &options); + assert((err == OK && buffer != NULL) || (err != OK && buffer == NULL)); + + if (err == ERROR_END_OF_STREAM || err != OK) { + eof = true; + continue; + } + + if (buffer->range_length() == 0) { + // The final buffer is empty. + buffer->release(); + continue; + } + + int32_t units, scale; + bool success = + buffer->meta_data()->findInt32(kKeyTimeUnits, &units); + assert(success); + success = + buffer->meta_data()->findInt32(kKeyTimeScale, &scale); + assert(success); + + int64_t pts_us = (int64_t)units * 1000000 / scale; + { + Mutex::Autolock autoLock(mLock); + mVideoPosition = pts_us; + } + + if (seeking && mAudioPlayer != NULL) { + // Now that we know where exactly video seeked (taking sync-samples + // into account), we will seek the audio track to the same time. + mAudioPlayer->seekTo(pts_us); + } + + if (firstFrame || seeking) { + mTimeSourceDeltaUs = mTimeSource->getRealTimeUs() - pts_us; + firstFrame = false; + } + + displayOrDiscardFrame(buffer, pts_us); + } + + mVideoDecoder->stop(); +} + +void MediaPlayerImpl::displayOrDiscardFrame( + MediaBuffer *buffer, int64_t pts_us) { + for (;;) { + if (!mPlaying || mPaused) { + buffer->release(); + buffer = NULL; + + return; + } + + int64_t realtime_us, mediatime_us; + if (mAudioPlayer != NULL + && mAudioPlayer->getMediaTimeMapping(&realtime_us, &mediatime_us)) { + mTimeSourceDeltaUs = realtime_us - mediatime_us; + } + + int64_t now_us = mTimeSource->getRealTimeUs(); + now_us -= mTimeSourceDeltaUs; + + int64_t delay_us = pts_us - now_us; + + if (delay_us < -15000) { + // We're late. + + LOGI("we're late by %lld ms, dropping a frame\n", + -delay_us / 1000); + + buffer->release(); + buffer = NULL; + return; + } else if (delay_us > 100000) { + LOGI("we're much too early (by %lld ms)\n", + delay_us / 1000); + usleep(100000); + continue; + } else if (delay_us > 0) { + usleep(delay_us); + } + + break; + } + + { + Mutex::Autolock autoLock(mLock); + if (mRenderer != NULL) { + sendFrameToISurface(buffer); + } + } + + buffer->release(); + buffer = NULL; +} + +void MediaPlayerImpl::init() { + if (mExtractor != NULL) { + int num_tracks; + assert(mExtractor->countTracks(&num_tracks) == OK); + + mDuration = 0; + + for (int i = 0; i < num_tracks; ++i) { + const sp<MetaData> meta = mExtractor->getTrackMetaData(i); + assert(meta != NULL); + + const char *mime; + if (!meta->findCString(kKeyMIMEType, &mime)) { + continue; + } + + bool is_audio = false; + bool is_acceptable = false; + if (!strncasecmp(mime, "audio/", 6)) { + is_audio = true; + is_acceptable = (mAudioSource == NULL); + } else if (!strncasecmp(mime, "video/", 6)) { + is_acceptable = (mVideoSource == NULL); + } + + if (!is_acceptable) { + continue; + } + + MediaSource *source; + if (mExtractor->getTrack(i, &source) != OK) { + continue; + } + + int32_t units, scale; + if (meta->findInt32(kKeyDuration, &units) + && meta->findInt32(kKeyTimeScale, &scale)) { + int64_t duration_us = (int64_t)units * 1000000 / scale; + if (duration_us > mDuration) { + mDuration = duration_us; + } + } + + if (is_audio) { + setAudioSource(source); + } else { + setVideoSource(source); + } + } + } +} + +void MediaPlayerImpl::setAudioSource(MediaSource *source) { + mAudioSource = source; + + sp<MetaData> meta = source->getFormat(); + + mAudioDecoder = OMXDecoder::Create(&mClient, meta); + mAudioDecoder->setSource(source); +} + +void MediaPlayerImpl::setVideoSource(MediaSource *source) { + LOGI("setVideoSource"); + mVideoSource = source; + + sp<MetaData> meta = source->getFormat(); + + bool success = meta->findInt32(kKeyWidth, &mVideoWidth); + assert(success); + + success = meta->findInt32(kKeyHeight, &mVideoHeight); + assert(success); + + mVideoDecoder = OMXDecoder::Create(&mClient, meta); + ((OMXDecoder *)mVideoDecoder)->setSource(source); + + if (mISurface.get() != NULL || mSurface.get() != NULL) { + depopulateISurface(); + populateISurface(); + } +} + +void MediaPlayerImpl::setSurface(const sp<Surface> &surface) { + LOGI("setSurface %p", surface.get()); + Mutex::Autolock autoLock(mLock); + + depopulateISurface(); + + mSurface = surface; + mISurface = NULL; + + if (mSurface.get() != NULL) { + populateISurface(); + } +} + +void MediaPlayerImpl::setISurface(const sp<ISurface> &isurface) { + LOGI("setISurface %p", isurface.get()); + Mutex::Autolock autoLock(mLock); + + depopulateISurface(); + + mSurface = NULL; + mISurface = isurface; + + if (mISurface.get() != NULL) { + populateISurface(); + } +} + +MediaSource *MediaPlayerImpl::makeShoutcastSource(const char *uri) { + if (strncasecmp(uri, "shoutcast://", 12)) { + return NULL; + } + + string host; + string path; + int port; + + char *slash = strchr(uri + 12, '/'); + if (slash == NULL) { + host = uri + 12; + path = "/"; + } else { + host = string(uri + 12, slash - (uri + 12)); + path = slash; + } + + char *colon = strchr(host.c_str(), ':'); + if (colon == NULL) { + port = 80; + } else { + char *end; + long tmp = strtol(colon + 1, &end, 10); + assert(end > colon + 1); + assert(tmp > 0 && tmp < 65536); + port = tmp; + + host = string(host, 0, colon - host.c_str()); + } + + LOGI("Connecting to host '%s', port %d, path '%s'", + host.c_str(), port, path.c_str()); + + HTTPStream *http = new HTTPStream; + int http_status; + + for (;;) { + status_t err = http->connect(host.c_str(), port); + assert(err == OK); + + err = http->send("GET "); + err = http->send(path.c_str()); + err = http->send(" HTTP/1.1\r\n"); + err = http->send("Host: "); + err = http->send(host.c_str()); + err = http->send("\r\n"); + err = http->send("Icy-MetaData: 1\r\n\r\n"); + + assert(OK == http->receive_header(&http_status)); + + if (http_status == 301 || http_status == 302) { + string location; + assert(http->find_header_value("Location", &location)); + + assert(string(location, 0, 7) == "http://"); + location.erase(0, 7); + string::size_type slashPos = location.find('/'); + if (slashPos == string::npos) { + slashPos = location.size(); + location += '/'; + } + + http->disconnect(); + + LOGI("Redirecting to %s\n", location.c_str()); + + host = string(location, 0, slashPos); + + string::size_type colonPos = host.find(':'); + if (colonPos != string::npos) { + const char *start = host.c_str() + colonPos + 1; + char *end; + long tmp = strtol(start, &end, 10); + assert(end > start && *end == '\0'); + + port = (tmp >= 0 && tmp < 65536) ? (int)tmp : 80; + } else { + port = 80; + } + + path = string(location, slashPos); + + continue; + } + + break; + } + + if (http_status != 200) { + LOGE("Connection failed: http_status = %d", http_status); + return NULL; + } + + MediaSource *source = new ShoutcastSource(http); + + return source; +} + +bool MediaPlayerImpl::isPlaying() const { + return mPlaying && !mPaused; +} + +int64_t MediaPlayerImpl::getDuration() { + return mDuration; +} + +int64_t MediaPlayerImpl::getPosition() { + int64_t position = 0; + if (mVideoSource != NULL) { + Mutex::Autolock autoLock(mLock); + position = mVideoPosition; + } else if (mAudioPlayer != NULL) { + position = mAudioPlayer->getMediaTimeUs(); + } + + return position; +} + +status_t MediaPlayerImpl::seekTo(int64_t time) { + LOGI("seekTo %lld", time); + + if (mPaused) { + return UNKNOWN_ERROR; + } + + if (mVideoSource == NULL && mAudioPlayer != NULL) { + mAudioPlayer->seekTo(time); + } else { + Mutex::Autolock autoLock(mLock); + mSeekTimeUs = time; + mSeeking = true; + } + + return OK; +} + +void MediaPlayerImpl::populateISurface() { + if (mVideoSource == NULL) { + return; + } + + sp<MetaData> meta = mVideoDecoder->getFormat(); + + int32_t format; + const char *component; + int32_t decodedWidth, decodedHeight; + bool success = meta->findInt32(kKeyColorFormat, &format); + success = success && meta->findCString(kKeyDecoderComponent, &component); + success = success && meta->findInt32(kKeyWidth, &decodedWidth); + success = success && meta->findInt32(kKeyHeight, &decodedHeight); + assert(success); + + if (mSurface.get() != NULL) { + mRenderer = + new SurfaceRenderer( + mSurface, mVideoWidth, mVideoHeight, + decodedWidth, decodedHeight); + } else if (format == OMX_COLOR_FormatYUV420Planar + && !strncasecmp(component, "OMX.qcom.video.decoder.", 23)) { + mRenderer = + new QComHardwareRenderer( + mISurface, mVideoWidth, mVideoHeight, + decodedWidth, decodedHeight); + } else { + LOGW("Using software renderer."); + mRenderer = new SoftwareRenderer( + mISurface, mVideoWidth, mVideoHeight, + decodedWidth, decodedHeight); + } +} + +void MediaPlayerImpl::depopulateISurface() { + delete mRenderer; + mRenderer = NULL; +} + +void MediaPlayerImpl::sendFrameToISurface(MediaBuffer *buffer) { + void *platformPrivate; + if (!buffer->meta_data()->findPointer( + kKeyPlatformPrivate, &platformPrivate)) { + platformPrivate = NULL; + } + + mRenderer->render( + (const uint8_t *)buffer->data() + buffer->range_offset(), + buffer->range_length(), + platformPrivate); +} + +void MediaPlayerImpl::setAudioSink( + const sp<MediaPlayerBase::AudioSink> &audioSink) { + LOGI("setAudioSink %p", audioSink.get()); + mAudioSink = audioSink; +} + +} // namespace android + diff --git a/media/libstagefright/MediaSource.cpp b/media/libstagefright/MediaSource.cpp new file mode 100644 index 0000000..ec89b74 --- /dev/null +++ b/media/libstagefright/MediaSource.cpp @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2009 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. + */ + +#include <media/stagefright/MediaSource.h> + +namespace android { + +MediaSource::MediaSource() {} + +MediaSource::~MediaSource() {} + +//////////////////////////////////////////////////////////////////////////////// + +MediaSource::ReadOptions::ReadOptions() { + reset(); +} + +void MediaSource::ReadOptions::reset() { + mOptions = 0; + mSeekTimeUs = 0; + mLatenessUs = 0; +} + +void MediaSource::ReadOptions::setSeekTo(int64_t time_us) { + mOptions |= kSeekTo_Option; + mSeekTimeUs = time_us; +} + +void MediaSource::ReadOptions::clearSeekTo() { + mOptions &= ~kSeekTo_Option; + mSeekTimeUs = 0; +} + +bool MediaSource::ReadOptions::getSeekTo(int64_t *time_us) const { + *time_us = mSeekTimeUs; + return (mOptions & kSeekTo_Option) != 0; +} + +void MediaSource::ReadOptions::setLateBy(int64_t lateness_us) { + mLatenessUs = lateness_us; +} + +int64_t MediaSource::ReadOptions::getLateBy() const { + return mLatenessUs; +} + +} // namespace android diff --git a/media/libstagefright/MetaData.cpp b/media/libstagefright/MetaData.cpp new file mode 100644 index 0000000..5d23732 --- /dev/null +++ b/media/libstagefright/MetaData.cpp @@ -0,0 +1,232 @@ +/* + * Copyright (C) 2009 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. + */ + +#include <assert.h> +#include <stdlib.h> +#include <string.h> + +#include <media/stagefright/MetaData.h> + +namespace android { + +MetaData::MetaData() { +} + +MetaData::MetaData(const MetaData &from) + : RefBase(), + mItems(from.mItems) { +} + +MetaData::~MetaData() { + clear(); +} + +void MetaData::clear() { + mItems.clear(); +} + +bool MetaData::remove(uint32_t key) { + ssize_t i = mItems.indexOfKey(key); + + if (i < 0) { + return false; + } + + mItems.removeItemsAt(i); + + return true; +} + +bool MetaData::setCString(uint32_t key, const char *value) { + return setData(key, TYPE_C_STRING, value, strlen(value) + 1); +} + +bool MetaData::setInt32(uint32_t key, int32_t value) { + return setData(key, TYPE_INT32, &value, sizeof(value)); +} + +bool MetaData::setFloat(uint32_t key, float value) { + return setData(key, TYPE_FLOAT, &value, sizeof(value)); +} + +bool MetaData::setPointer(uint32_t key, void *value) { + return setData(key, TYPE_POINTER, &value, sizeof(value)); +} + +bool MetaData::findCString(uint32_t key, const char **value) { + uint32_t type; + const void *data; + size_t size; + if (!findData(key, &type, &data, &size) || type != TYPE_C_STRING) { + return false; + } + + *value = (const char *)data; + + return true; +} + +bool MetaData::findInt32(uint32_t key, int32_t *value) { + uint32_t type; + const void *data; + size_t size; + if (!findData(key, &type, &data, &size) || type != TYPE_INT32) { + return false; + } + + assert(size == sizeof(*value)); + + *value = *(int32_t *)data; + + return true; +} + +bool MetaData::findFloat(uint32_t key, float *value) { + uint32_t type; + const void *data; + size_t size; + if (!findData(key, &type, &data, &size) || type != TYPE_FLOAT) { + return false; + } + + assert(size == sizeof(*value)); + + *value = *(float *)data; + + return true; +} + +bool MetaData::findPointer(uint32_t key, void **value) { + uint32_t type; + const void *data; + size_t size; + if (!findData(key, &type, &data, &size) || type != TYPE_POINTER) { + return false; + } + + assert(size == sizeof(*value)); + + *value = *(void **)data; + + return true; +} + +bool MetaData::setData( + uint32_t key, uint32_t type, const void *data, size_t size) { + bool overwrote_existing = true; + + ssize_t i = mItems.indexOfKey(key); + if (i < 0) { + typed_data item; + i = mItems.add(key, item); + + overwrote_existing = false; + } + + typed_data &item = mItems.editValueAt(i); + + item.setData(type, data, size); + + return overwrote_existing; +} + +bool MetaData::findData(uint32_t key, uint32_t *type, + const void **data, size_t *size) const { + ssize_t i = mItems.indexOfKey(key); + + if (i < 0) { + return false; + } + + const typed_data &item = mItems.valueAt(i); + + item.getData(type, data, size); + + return true; +} + +MetaData::typed_data::typed_data() + : mType(0), + mSize(0) { +} + +MetaData::typed_data::~typed_data() { + clear(); +} + +MetaData::typed_data::typed_data(const typed_data &from) + : mType(from.mType), + mSize(0) { + allocateStorage(from.mSize); + memcpy(storage(), from.storage(), mSize); +} + +MetaData::typed_data &MetaData::typed_data::operator=( + const MetaData::typed_data &from) { + if (this != &from) { + clear(); + mType = from.mType; + allocateStorage(from.mSize); + memcpy(storage(), from.storage(), mSize); + } + + return *this; +} + +void MetaData::typed_data::clear() { + freeStorage(); + + mType = 0; +} + +void MetaData::typed_data::setData( + uint32_t type, const void *data, size_t size) { + clear(); + + mType = type; + allocateStorage(size); + memcpy(storage(), data, size); +} + +void MetaData::typed_data::getData( + uint32_t *type, const void **data, size_t *size) const { + *type = mType; + *size = mSize; + *data = storage(); +} + +void MetaData::typed_data::allocateStorage(size_t size) { + mSize = size; + + if (usesReservoir()) { + return; + } + + u.ext_data = malloc(mSize); +} + +void MetaData::typed_data::freeStorage() { + if (!usesReservoir()) { + if (u.ext_data) { + free(u.ext_data); + } + } + + mSize = 0; +} + +} // namespace android + diff --git a/media/libstagefright/MmapSource.cpp b/media/libstagefright/MmapSource.cpp new file mode 100644 index 0000000..7cb861c --- /dev/null +++ b/media/libstagefright/MmapSource.cpp @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2009 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 "MmapSource" +#include <utils/Log.h> + +#include <sys/mman.h> + +#undef NDEBUG +#include <assert.h> + +#include <fcntl.h> +#include <string.h> +#include <unistd.h> + +#include <media/stagefright/MmapSource.h> + +namespace android { + +MmapSource::MmapSource(const char *filename) + : mFd(open(filename, O_RDONLY)), + mBase(NULL), + mSize(0) { + LOGV("MmapSource '%s'", filename); + assert(mFd >= 0); + + off_t size = lseek(mFd, 0, SEEK_END); + mSize = (size_t)size; + + mBase = mmap(0, mSize, PROT_READ, MAP_FILE | MAP_SHARED, mFd, 0); + + if (mBase == (void *)-1) { + mBase = NULL; + + close(mFd); + mFd = -1; + } +} + +MmapSource::MmapSource(int fd, int64_t offset, int64_t length) + : mFd(fd), + mBase(NULL), + mSize(length) { + LOGV("MmapSource fd:%d offset:%lld length:%lld", fd, offset, length); + assert(fd >= 0); + + mBase = mmap(0, mSize, PROT_READ, MAP_FILE | MAP_SHARED, mFd, offset); + + if (mBase == (void *)-1) { + mBase = NULL; + + close(mFd); + mFd = -1; + } + +} + +MmapSource::~MmapSource() { + if (mFd != -1) { + munmap(mBase, mSize); + mBase = NULL; + mSize = 0; + + close(mFd); + mFd = -1; + } +} + +status_t MmapSource::InitCheck() const { + return mFd == -1 ? NO_INIT : OK; +} + +ssize_t MmapSource::read_at(off_t offset, void *data, size_t size) { + LOGV("read_at offset:%ld data:%p size:%d", offset, data, size); + assert(offset >= 0); + + size_t avail = 0; + if (offset >= 0 && offset < (off_t)mSize) { + avail = mSize - offset; + } + + if (size > avail) { + size = avail; + } + + memcpy(data, (const uint8_t *)mBase + offset, size); + + return (ssize_t)size; +} + +status_t MmapSource::getSize(off_t *size) { + *size = mSize; + + return OK; +} + +} // namespace android + diff --git a/media/libstagefright/OMXClient.cpp b/media/libstagefright/OMXClient.cpp new file mode 100644 index 0000000..1bc8a44 --- /dev/null +++ b/media/libstagefright/OMXClient.cpp @@ -0,0 +1,369 @@ +/* + * Copyright (C) 2009 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 "OMXClient" +#include <utils/Log.h> + +#include <sys/socket.h> + +#undef NDEBUG +#include <assert.h> + +#include <binder/IServiceManager.h> +#include <media/IMediaPlayerService.h> +#include <media/IOMX.h> +#include <media/stagefright/OMXClient.h> + +namespace android { + +OMXClient::OMXClient() + : mSock(-1) { +} + +OMXClient::~OMXClient() { + disconnect(); +} + +status_t OMXClient::connect() { + Mutex::Autolock autoLock(mLock); + + if (mSock >= 0) { + return UNKNOWN_ERROR; + } + + sp<IServiceManager> sm = defaultServiceManager(); + sp<IBinder> binder = sm->getService(String16("media.player")); + sp<IMediaPlayerService> service = interface_cast<IMediaPlayerService>(binder); + + assert(service.get() != NULL); + + mOMX = service->createOMX(); + assert(mOMX.get() != NULL); + +#if IOMX_USES_SOCKETS + status_t result = mOMX->connect(&mSock); + if (result != OK) { + mSock = -1; + + mOMX = NULL; + return result; + } + + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); + + int err = pthread_create(&mThread, &attr, ThreadWrapper, this); + assert(err == 0); + + pthread_attr_destroy(&attr); +#else + mReflector = new OMXClientReflector(this); +#endif + + return OK; +} + +void OMXClient::disconnect() { + { + Mutex::Autolock autoLock(mLock); + + if (mSock < 0) { + return; + } + + assert(mObservers.isEmpty()); + } + +#if IOMX_USES_SOCKETS + omx_message msg; + msg.type = omx_message::DISCONNECT; + ssize_t n = send(mSock, &msg, sizeof(msg), 0); + assert(n > 0 && static_cast<size_t>(n) == sizeof(msg)); + + void *dummy; + pthread_join(mThread, &dummy); +#else + mReflector->reset(); + mReflector.clear(); +#endif +} + +#if IOMX_USES_SOCKETS +// static +void *OMXClient::ThreadWrapper(void *me) { + ((OMXClient *)me)->threadEntry(); + + return NULL; +} + +void OMXClient::threadEntry() { + bool done = false; + while (!done) { + omx_message msg; + ssize_t n = recv(mSock, &msg, sizeof(msg), 0); + + if (n <= 0) { + break; + } + + done = onOMXMessage(msg); + } + + Mutex::Autolock autoLock(mLock); + close(mSock); + mSock = -1; +} +#endif + +status_t OMXClient::fillBuffer(IOMX::node_id node, IOMX::buffer_id buffer) { +#if !IOMX_USES_SOCKETS + mOMX->fill_buffer(node, buffer); +#else + if (mSock < 0) { + return UNKNOWN_ERROR; + } + + omx_message msg; + msg.type = omx_message::FILL_BUFFER; + msg.u.buffer_data.node = node; + msg.u.buffer_data.buffer = buffer; + + ssize_t n = send(mSock, &msg, sizeof(msg), 0); + assert(n > 0 && static_cast<size_t>(n) == sizeof(msg)); +#endif + + return OK; +} + +status_t OMXClient::emptyBuffer( + IOMX::node_id node, IOMX::buffer_id buffer, + OMX_U32 range_offset, OMX_U32 range_length, + OMX_U32 flags, OMX_TICKS timestamp) { +#if !IOMX_USES_SOCKETS + mOMX->empty_buffer( + node, buffer, range_offset, range_length, flags, timestamp); +#else + if (mSock < 0) { + return UNKNOWN_ERROR; + } + + // XXX I don't like all this copying... + + omx_message msg; + msg.type = omx_message::EMPTY_BUFFER; + msg.u.extended_buffer_data.node = node; + msg.u.extended_buffer_data.buffer = buffer; + msg.u.extended_buffer_data.range_offset = range_offset; + msg.u.extended_buffer_data.range_length = range_length; + msg.u.extended_buffer_data.flags = flags; + msg.u.extended_buffer_data.timestamp = timestamp; + + ssize_t n = send(mSock, &msg, sizeof(msg), 0); + assert(n > 0 && static_cast<size_t>(n) == sizeof(msg)); +#endif + + return OK; +} + +status_t OMXClient::send_command( + IOMX::node_id node, OMX_COMMANDTYPE cmd, OMX_S32 param) { +#if !IOMX_USES_SOCKETS + return mOMX->send_command(node, cmd, param); +#else + if (mSock < 0) { + return UNKNOWN_ERROR; + } + + omx_message msg; + msg.type = omx_message::SEND_COMMAND; + msg.u.send_command_data.node = node; + msg.u.send_command_data.cmd = cmd; + msg.u.send_command_data.param = param; + + ssize_t n = send(mSock, &msg, sizeof(msg), 0); + assert(n > 0 && static_cast<size_t>(n) == sizeof(msg)); +#endif + + return OK; +} + +status_t OMXClient::registerObserver( + IOMX::node_id node, OMXObserver *observer) { + Mutex::Autolock autoLock(&mLock); + + ssize_t index = mObservers.indexOfKey(node); + if (index >= 0) { + return UNKNOWN_ERROR; + } + + mObservers.add(node, observer); + observer->start(); + +#if !IOMX_USES_SOCKETS + mOMX->observe_node(node, mReflector); +#endif + + return OK; +} + +void OMXClient::unregisterObserver(IOMX::node_id node) { + Mutex::Autolock autoLock(mLock); + + ssize_t index = mObservers.indexOfKey(node); + assert(index >= 0); + + if (index < 0) { + return; + } + + OMXObserver *observer = mObservers.valueAt(index); + observer->stop(); + mObservers.removeItemsAt(index); +} + +bool OMXClient::onOMXMessage(const omx_message &msg) { + bool done = false; + + switch (msg.type) { + case omx_message::EVENT: + { + LOGV("OnEvent node:%p event:%d data1:%ld data2:%ld", + msg.u.event_data.node, + msg.u.event_data.event, + msg.u.event_data.data1, + msg.u.event_data.data2); + + break; + } + + case omx_message::FILL_BUFFER_DONE: + { + LOGV("FillBufferDone %p", msg.u.extended_buffer_data.buffer); + break; + } + + case omx_message::EMPTY_BUFFER_DONE: + { + LOGV("EmptyBufferDone %p", msg.u.buffer_data.buffer); + break; + } + +#if IOMX_USES_SOCKETS + case omx_message::DISCONNECTED: + { + LOGV("Disconnected"); + done = true; + break; + } +#endif + + default: + LOGE("received unknown omx_message type %d", msg.type); + break; + } + + Mutex::Autolock autoLock(mLock); + ssize_t index = mObservers.indexOfKey(msg.u.buffer_data.node); + + if (index >= 0) { + mObservers.editValueAt(index)->postMessage(msg); + } + + return done; +} + +//////////////////////////////////////////////////////////////////////////////// + +OMXObserver::OMXObserver() { +} + +OMXObserver::~OMXObserver() { +} + +void OMXObserver::start() { + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); + + int err = pthread_create(&mThread, &attr, ThreadWrapper, this); + assert(err == 0); + + pthread_attr_destroy(&attr); +} + +void OMXObserver::stop() { + omx_message msg; + msg.type = omx_message::QUIT_OBSERVER; + postMessage(msg); + + void *dummy; + pthread_join(mThread, &dummy); +} + +void OMXObserver::postMessage(const omx_message &msg) { + Mutex::Autolock autoLock(mLock); + mQueue.push_back(msg); + mQueueNotEmpty.signal(); +} + +// static +void *OMXObserver::ThreadWrapper(void *me) { + static_cast<OMXObserver *>(me)->threadEntry(); + + return NULL; +} + +void OMXObserver::threadEntry() { + for (;;) { + omx_message msg; + + { + Mutex::Autolock autoLock(mLock); + while (mQueue.empty()) { + mQueueNotEmpty.wait(mLock); + } + + msg = *mQueue.begin(); + mQueue.erase(mQueue.begin()); + } + + if (msg.type == omx_message::QUIT_OBSERVER) { + break; + } + + onOMXMessage(msg); + } +} + +//////////////////////////////////////////////////////////////////////////////// + +OMXClientReflector::OMXClientReflector(OMXClient *client) + : mClient(client) { +} + +void OMXClientReflector::on_message(const omx_message &msg) { + if (mClient != NULL) { + mClient->onOMXMessage(msg); + } +} + +void OMXClientReflector::reset() { + mClient = NULL; +} + +} // namespace android diff --git a/media/libstagefright/OMXDecoder.cpp b/media/libstagefright/OMXDecoder.cpp new file mode 100644 index 0000000..c059a9d --- /dev/null +++ b/media/libstagefright/OMXDecoder.cpp @@ -0,0 +1,1329 @@ +/* + * Copyright (C) 2009 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 "OMXDecoder" +#include <utils/Log.h> + +#undef NDEBUG +#include <assert.h> + +#include <OMX_Component.h> + +#include <media/stagefright/ESDS.h> +#include <media/stagefright/MetaData.h> +#include <media/stagefright/OMXDecoder.h> + +namespace android { + +class OMXMediaBuffer : public MediaBuffer { +public: + OMXMediaBuffer(IOMX::buffer_id buffer_id, const sp<IMemory> &mem) + : MediaBuffer(mem->pointer(), + mem->size()), + mBufferID(buffer_id), + mMem(mem) { + } + + IOMX::buffer_id buffer_id() const { return mBufferID; } + +private: + IOMX::buffer_id mBufferID; + sp<IMemory> mMem; + + OMXMediaBuffer(const OMXMediaBuffer &); + OMXMediaBuffer &operator=(const OMXMediaBuffer &); +}; + +struct CodecInfo { + const char *mime; + const char *codec; +}; + +static const CodecInfo kDecoderInfo[] = { + { "audio/mpeg", "OMX.PV.mp3dec" }, + { "audio/3gpp", "OMX.PV.amrdec" }, + { "audio/mp4a-latm", "OMX.PV.aacdec" }, + { "video/mp4v-es", "OMX.qcom.video.decoder.mpeg4" }, + { "video/mp4v-es", "OMX.PV.mpeg4dec" }, + { "video/3gpp", "OMX.qcom.video.decoder.h263" }, + { "video/3gpp", "OMX.PV.h263dec" }, + { "video/avc", "OMX.qcom.video.decoder.avc" }, + { "video/avc", "OMX.PV.avcdec" }, +}; + +static const CodecInfo kEncoderInfo[] = { + { "audio/3gpp", "OMX.PV.amrencnb" }, + { "audio/mp4a-latm", "OMX.PV.aacenc" }, + { "video/mp4v-es", "OMX.qcom.video.encoder.mpeg4" }, + { "video/mp4v-es", "OMX.PV.mpeg4enc" }, + { "video/3gpp", "OMX.qcom.video.encoder.h263" }, + { "video/3gpp", "OMX.PV.h263enc" }, + { "video/avc", "OMX.PV.avcenc" }, +}; + +static const char *GetCodec(const CodecInfo *info, size_t numInfos, + const char *mime, int index) { + assert(index >= 0); + for(size_t i = 0; i < numInfos; ++i) { + if (!strcasecmp(mime, info[i].mime)) { + if (index == 0) { + return info[i].codec; + } + + --index; + } + } + + return NULL; +} + +// static +OMXDecoder *OMXDecoder::Create(OMXClient *client, const sp<MetaData> &meta) { + const char *mime; + bool success = meta->findCString(kKeyMIMEType, &mime); + assert(success); + + sp<IOMX> omx = client->interface(); + + const char *codec = NULL; + IOMX::node_id node = 0; + for (int index = 0;; ++index) { + codec = GetCodec( + kDecoderInfo, sizeof(kDecoderInfo) / sizeof(kDecoderInfo[0]), + mime, index); + + if (!codec) { + return NULL; + } + + LOGI("Attempting to allocate OMX node '%s'", codec); + + status_t err = omx->allocate_node(codec, &node); + if (err == OK) { + break; + } + } + + OMXDecoder *decoder = new OMXDecoder(client, node, mime, codec); + + uint32_t type; + const void *data; + size_t size; + if (meta->findData(kKeyESDS, &type, &data, &size)) { + ESDS esds((const char *)data, size); + assert(esds.InitCheck() == OK); + + const void *codec_specific_data; + size_t codec_specific_data_size; + esds.getCodecSpecificInfo( + &codec_specific_data, &codec_specific_data_size); + + printf("found codec specific data of size %d\n", + codec_specific_data_size); + + decoder->addCodecSpecificData( + codec_specific_data, codec_specific_data_size); + } else if (meta->findData(kKeyAVCC, &type, &data, &size)) { + printf("found avcc of size %d\n", size); + + const uint8_t *ptr = (const uint8_t *)data + 6; + size -= 6; + while (size >= 2) { + size_t length = ptr[0] << 8 | ptr[1]; + + ptr += 2; + size -= 2; + + // printf("length = %d, size = %d\n", length, size); + + assert(size >= length); + + decoder->addCodecSpecificData(ptr, length); + + ptr += length; + size -= length; + + if (size <= 1) { + break; + } + + ptr++; // XXX skip trailing 0x01 byte??? + --size; + } + } + + return decoder; +} + +// static +OMXDecoder *OMXDecoder::CreateEncoder( + OMXClient *client, const sp<MetaData> &meta) { + const char *mime; + bool success = meta->findCString(kKeyMIMEType, &mime); + assert(success); + + sp<IOMX> omx = client->interface(); + + const char *codec = NULL; + IOMX::node_id node = 0; + for (int index = 0;; ++index) { + codec = GetCodec( + kEncoderInfo, sizeof(kEncoderInfo) / sizeof(kEncoderInfo[0]), + mime, index); + + if (!codec) { + return NULL; + } + + LOGI("Attempting to allocate OMX node '%s'", codec); + + status_t err = omx->allocate_node(codec, &node); + if (err == OK) { + break; + } + } + + OMXDecoder *encoder = new OMXDecoder(client, node, mime, codec); + + return encoder; +} + +OMXDecoder::OMXDecoder(OMXClient *client, IOMX::node_id node, + const char *mime, const char *codec) + : mClient(client), + mOMX(mClient->interface()), + mNode(node), + mComponentName(strdup(codec)), + mIsMP3(!strcasecmp(mime, "audio/mpeg")), + mSource(NULL), + mCodecSpecificDataIterator(mCodecSpecificData.begin()), + mState(OMX_StateLoaded), + mPortStatusMask(kPortStatusActive << 2 | kPortStatusActive), + mShutdownInitiated(false), + mDealer(new MemoryDealer(3 * 1024 * 1024)), + mSeeking(false), + mStarted(false), + mErrorCondition(OK), + mReachedEndOfInput(false) { + mClient->registerObserver(mNode, this); + + mBuffers.push(); // input buffers + mBuffers.push(); // output buffers +} + +OMXDecoder::~OMXDecoder() { + if (mStarted) { + stop(); + } + + for (List<CodecSpecificData>::iterator it = mCodecSpecificData.begin(); + it != mCodecSpecificData.end(); ++it) { + free((*it).data); + } + mCodecSpecificData.clear(); + + mClient->unregisterObserver(mNode); + + status_t err = mOMX->free_node(mNode); + assert(err == OK); + mNode = 0; + + free(mComponentName); + mComponentName = NULL; +} + +void OMXDecoder::setSource(MediaSource *source) { + Mutex::Autolock autoLock(mLock); + + assert(mSource == NULL); + + mSource = source; + setup(); +} + +status_t OMXDecoder::start(MetaData *) { + assert(!mStarted); + + // mDealer->dump("Decoder Dealer"); + + sp<MetaData> params = new MetaData; + if (!strcmp(mComponentName, "OMX.qcom.video.decoder.avc")) { + params->setInt32(kKeyNeedsNALFraming, true); + } + + status_t err = mSource->start(params.get()); + + if (err != OK) { + return err; + } + + postStart(); + + mStarted = true; + + return OK; +} + +status_t OMXDecoder::stop() { + assert(mStarted); + + LOGI("Initiating OMX Node shutdown, busy polling."); + initiateShutdown(); + + // Important: initiateShutdown must be called first, _then_ release + // buffers we're holding onto. + while (!mOutputBuffers.empty()) { + MediaBuffer *buffer = *mOutputBuffers.begin(); + mOutputBuffers.erase(mOutputBuffers.begin()); + + LOGV("releasing buffer %p.", buffer->data()); + + buffer->release(); + buffer = NULL; + } + + int attempt = 1; + while (mState != OMX_StateLoaded && attempt < 10) { + usleep(100000); + + ++attempt; + } + + if (mState != OMX_StateLoaded) { + LOGE("!!! OMX Node '%s' did NOT shutdown cleanly !!!", mComponentName); + } else { + LOGI("OMX Node '%s' has shutdown cleanly.", mComponentName); + } + + mSource->stop(); + + mCodecSpecificDataIterator = mCodecSpecificData.begin(); + mShutdownInitiated = false; + mSeeking = false; + mStarted = false; + mErrorCondition = OK; + mReachedEndOfInput = false; + + return OK; +} + +sp<MetaData> OMXDecoder::getFormat() { + return mOutputFormat; +} + +status_t OMXDecoder::read( + MediaBuffer **out, const ReadOptions *options) { + assert(mStarted); + + *out = NULL; + + Mutex::Autolock autoLock(mLock); + + if (mErrorCondition != OK && mErrorCondition != ERROR_END_OF_STREAM) { + // Errors are sticky. + return mErrorCondition; + } + + int64_t seekTimeUs; + if (options && options->getSeekTo(&seekTimeUs)) { + LOGI("[%s] seeking to %lld us", mComponentName, seekTimeUs); + + mErrorCondition = OK; + mReachedEndOfInput = false; + + setPortStatus(kPortIndexInput, kPortStatusFlushing); + setPortStatus(kPortIndexOutput, kPortStatusFlushing); + + mSeeking = true; + mSeekTimeUs = seekTimeUs; + + while (!mOutputBuffers.empty()) { + OMXMediaBuffer *buffer = + static_cast<OMXMediaBuffer *>(*mOutputBuffers.begin()); + + // We could have used buffer->release() instead, but we're + // holding the lock and signalBufferReturned attempts to acquire + // the lock. + buffer->claim(); + mBuffers.editItemAt( + kPortIndexOutput).push_back(buffer->buffer_id()); + buffer = NULL; + + mOutputBuffers.erase(mOutputBuffers.begin()); + } + + status_t err = mOMX->send_command(mNode, OMX_CommandFlush, -1); + assert(err == OK); + + // Once flushing is completed buffers will again be scheduled to be + // filled/emptied. + } + + while (mErrorCondition == OK && mOutputBuffers.empty()) { + mOutputBufferAvailable.wait(mLock); + } + + if (!mOutputBuffers.empty()) { + MediaBuffer *buffer = *mOutputBuffers.begin(); + mOutputBuffers.erase(mOutputBuffers.begin()); + + *out = buffer; + + return OK; + } else { + assert(mErrorCondition != OK); + return mErrorCondition; + } +} + +void OMXDecoder::addCodecSpecificData(const void *data, size_t size) { + CodecSpecificData specific; + specific.data = malloc(size); + memcpy(specific.data, data, size); + specific.size = size; + + mCodecSpecificData.push_back(specific); + mCodecSpecificDataIterator = mCodecSpecificData.begin(); +} + +void OMXDecoder::onOMXMessage(const omx_message &msg) { + Mutex::Autolock autoLock(mLock); + + switch (msg.type) { + case omx_message::START: + { + onStart(); + break; + } + + case omx_message::EVENT: + { + onEvent(msg.u.event_data.event, msg.u.event_data.data1, + msg.u.event_data.data2); + break; + } + + case omx_message::EMPTY_BUFFER_DONE: + { + onEmptyBufferDone(msg.u.buffer_data.buffer); + break; + } + + case omx_message::FILL_BUFFER_DONE: + case omx_message::INITIAL_FILL_BUFFER: + { + onFillBufferDone(msg); + break; + } + + default: + LOGE("received unknown omx_message type %d", msg.type); + break; + } +} + +void OMXDecoder::setAMRFormat() { + OMX_AUDIO_PARAM_AMRTYPE def; + def.nSize = sizeof(def); + def.nVersion.s.nVersionMajor = 1; + def.nVersion.s.nVersionMinor = 1; + def.nPortIndex = kPortIndexInput; + + status_t err = + mOMX->get_parameter(mNode, OMX_IndexParamAudioAmr, &def, sizeof(def)); + + assert(err == NO_ERROR); + + def.eAMRFrameFormat = OMX_AUDIO_AMRFrameFormatFSF; + def.eAMRBandMode = OMX_AUDIO_AMRBandModeNB0; + + err = mOMX->set_parameter(mNode, OMX_IndexParamAudioAmr, &def, sizeof(def)); + assert(err == NO_ERROR); +} + +void OMXDecoder::setAACFormat() { + OMX_AUDIO_PARAM_AACPROFILETYPE def; + def.nSize = sizeof(def); + def.nVersion.s.nVersionMajor = 1; + def.nVersion.s.nVersionMinor = 1; + def.nPortIndex = kPortIndexInput; + + status_t err = + mOMX->get_parameter(mNode, OMX_IndexParamAudioAac, &def, sizeof(def)); + assert(err == NO_ERROR); + + def.eAACStreamFormat = OMX_AUDIO_AACStreamFormatMP4ADTS; + + err = mOMX->set_parameter(mNode, OMX_IndexParamAudioAac, &def, sizeof(def)); + assert(err == NO_ERROR); +} + +void OMXDecoder::setVideoOutputFormat(OMX_U32 width, OMX_U32 height) { + LOGI("setVideoOutputFormat width=%ld, height=%ld", width, height); + + OMX_PARAM_PORTDEFINITIONTYPE def; + OMX_VIDEO_PORTDEFINITIONTYPE *video_def = &def.format.video; + + bool is_encoder = strstr(mComponentName, ".encoder.") != NULL; // XXX + + def.nSize = sizeof(def); + def.nVersion.s.nVersionMajor = 1; + def.nVersion.s.nVersionMinor = 1; + def.nPortIndex = is_encoder ? kPortIndexOutput : kPortIndexInput; + + status_t err = mOMX->get_parameter( + mNode, OMX_IndexParamPortDefinition, &def, sizeof(def)); + + assert(err == NO_ERROR); + +#if 1 + // XXX Need a (much) better heuristic to compute input buffer sizes. + const size_t X = 64 * 1024; + if (def.nBufferSize < X) { + def.nBufferSize = X; + } +#endif + + assert(def.eDomain == OMX_PortDomainVideo); + + video_def->nFrameWidth = width; + video_def->nFrameHeight = height; + // video_def.eCompressionFormat = OMX_VIDEO_CodingAVC; + video_def->eColorFormat = OMX_COLOR_FormatUnused; + + err = mOMX->set_parameter( + mNode, OMX_IndexParamPortDefinition, &def, sizeof(def)); + assert(err == NO_ERROR); + + //////////////////////////////////////////////////////////////////////////// + + def.nSize = sizeof(def); + def.nVersion.s.nVersionMajor = 1; + def.nVersion.s.nVersionMinor = 1; + def.nPortIndex = is_encoder ? kPortIndexInput : kPortIndexOutput; + + err = mOMX->get_parameter( + mNode, OMX_IndexParamPortDefinition, &def, sizeof(def)); + assert(err == NO_ERROR); + + assert(def.eDomain == OMX_PortDomainVideo); + + def.nBufferSize = + (((width + 15) & -16) * ((height + 15) & -16) * 3) / 2; // YUV420 + + video_def->nFrameWidth = width; + video_def->nFrameHeight = height; + video_def->nStride = width; + // video_def->nSliceHeight = height; + video_def->eCompressionFormat = OMX_VIDEO_CodingUnused; +// video_def->eColorFormat = OMX_COLOR_FormatYUV420Planar; + + err = mOMX->set_parameter( + mNode, OMX_IndexParamPortDefinition, &def, sizeof(def)); + assert(err == NO_ERROR); +} + +void OMXDecoder::setup() { + const sp<MetaData> &meta = mSource->getFormat(); + + const char *mime; + bool success = meta->findCString(kKeyMIMEType, &mime); + assert(success); + + if (!strcasecmp(mime, "audio/3gpp")) { + setAMRFormat(); + } else if (!strcasecmp(mime, "audio/mp4a-latm")) { + setAACFormat(); + } else if (!strncasecmp(mime, "video/", 6)) { + int32_t width, height; + bool success = meta->findInt32(kKeyWidth, &width); + success = success && meta->findInt32(kKeyHeight, &height); + assert(success); + + setVideoOutputFormat(width, height); + } + + // dumpPortDefinition(0); + // dumpPortDefinition(1); + + mOutputFormat = new MetaData; + mOutputFormat->setCString(kKeyDecoderComponent, mComponentName); + + OMX_PARAM_PORTDEFINITIONTYPE def; + def.nSize = sizeof(def); + def.nVersion.s.nVersionMajor = 1; + def.nVersion.s.nVersionMinor = 1; + def.nPortIndex = kPortIndexOutput; + + status_t err = mOMX->get_parameter( + mNode, OMX_IndexParamPortDefinition, &def, sizeof(def)); + assert(err == NO_ERROR); + + switch (def.eDomain) { + case OMX_PortDomainAudio: + { + OMX_AUDIO_PORTDEFINITIONTYPE *audio_def = &def.format.audio; + + assert(audio_def->eEncoding == OMX_AUDIO_CodingPCM); + + OMX_AUDIO_PARAM_PCMMODETYPE params; + params.nSize = sizeof(params); + params.nVersion.s.nVersionMajor = 1; + params.nVersion.s.nVersionMinor = 1; + params.nPortIndex = kPortIndexOutput; + + err = mOMX->get_parameter( + mNode, OMX_IndexParamAudioPcm, ¶ms, sizeof(params)); + assert(err == OK); + + assert(params.eNumData == OMX_NumericalDataSigned); + assert(params.nBitPerSample == 16); + assert(params.ePCMMode == OMX_AUDIO_PCMModeLinear); + + int32_t numChannels, sampleRate; + meta->findInt32(kKeyChannelCount, &numChannels); + meta->findInt32(kKeySampleRate, &sampleRate); + + mOutputFormat->setCString(kKeyMIMEType, "audio/raw"); + mOutputFormat->setInt32(kKeyChannelCount, numChannels); + mOutputFormat->setInt32(kKeySampleRate, sampleRate); + break; + } + + case OMX_PortDomainVideo: + { + OMX_VIDEO_PORTDEFINITIONTYPE *video_def = &def.format.video; + + if (video_def->eCompressionFormat == OMX_VIDEO_CodingUnused) { + mOutputFormat->setCString(kKeyMIMEType, "video/raw"); + } else if (video_def->eCompressionFormat == OMX_VIDEO_CodingMPEG4) { + mOutputFormat->setCString(kKeyMIMEType, "video/mp4v-es"); + } else if (video_def->eCompressionFormat == OMX_VIDEO_CodingH263) { + mOutputFormat->setCString(kKeyMIMEType, "video/3gpp"); + } else if (video_def->eCompressionFormat == OMX_VIDEO_CodingAVC) { + mOutputFormat->setCString(kKeyMIMEType, "video/avc"); + } else { + assert(!"Unknown compression format."); + } + + if (!strcmp(mComponentName, "OMX.PV.avcdec")) { + // This component appears to be lying to me. + mOutputFormat->setInt32( + kKeyWidth, (video_def->nFrameWidth + 15) & -16); + mOutputFormat->setInt32( + kKeyHeight, (video_def->nFrameHeight + 15) & -16); + } else { + mOutputFormat->setInt32(kKeyWidth, video_def->nFrameWidth); + mOutputFormat->setInt32(kKeyHeight, video_def->nFrameHeight); + } + + mOutputFormat->setInt32(kKeyColorFormat, video_def->eColorFormat); + break; + } + + default: + { + assert(!"should not be here, neither audio nor video."); + break; + } + } +} + +void OMXDecoder::onStart() { + bool needs_qcom_hack = + !strncmp(mComponentName, "OMX.qcom.video.", 15); + + if (!needs_qcom_hack) { + status_t err = + mOMX->send_command(mNode, OMX_CommandStateSet, OMX_StateIdle); + assert(err == NO_ERROR); + } + + allocateBuffers(kPortIndexInput); + allocateBuffers(kPortIndexOutput); + + if (needs_qcom_hack) { + // XXX this should happen before AllocateBuffers, but qcom's + // h264 vdec disagrees. + status_t err = + mOMX->send_command(mNode, OMX_CommandStateSet, OMX_StateIdle); + assert(err == NO_ERROR); + } +} + +void OMXDecoder::allocateBuffers(OMX_U32 port_index) { + assert(mBuffers[port_index].empty()); + + OMX_U32 num_buffers; + OMX_U32 buffer_size; + + OMX_PARAM_PORTDEFINITIONTYPE def; + def.nSize = sizeof(def); + def.nVersion.s.nVersionMajor = 1; + def.nVersion.s.nVersionMinor = 1; + def.nVersion.s.nRevision = 0; + def.nVersion.s.nStep = 0; + def.nPortIndex = port_index; + + status_t err = mOMX->get_parameter( + mNode, OMX_IndexParamPortDefinition, &def, sizeof(def)); + assert(err == NO_ERROR); + + num_buffers = def.nBufferCountActual; + buffer_size = def.nBufferSize; + + LOGV("[%s] port %ld: allocating %ld buffers of size %ld each\n", + mComponentName, port_index, num_buffers, buffer_size); + + for (OMX_U32 i = 0; i < num_buffers; ++i) { + sp<IMemory> mem = mDealer->allocate(buffer_size); + assert(mem.get() != NULL); + + IOMX::buffer_id buffer; + status_t err; + + if (port_index == kPortIndexInput + && !strncmp(mComponentName, "OMX.qcom.video.encoder.", 23)) { + // qcom's H.263 encoder appears to want to allocate its own input + // buffers. + err = mOMX->allocate_buffer_with_backup(mNode, port_index, mem, &buffer); + if (err != OK) { + LOGE("[%s] allocate_buffer_with_backup failed with error %d", + mComponentName, err); + } + } else if (port_index == kPortIndexOutput + && !strncmp(mComponentName, "OMX.qcom.video.decoder.", 23)) { +#if 1 + err = mOMX->allocate_buffer_with_backup(mNode, port_index, mem, &buffer); +#else + // XXX This is fine as long as we are either running the player + // inside the media server process or we are using the + // QComHardwareRenderer to output the frames. + err = mOMX->allocate_buffer(mNode, port_index, buffer_size, &buffer); +#endif + if (err != OK) { + LOGE("[%s] allocate_buffer_with_backup failed with error %d", + mComponentName, err); + } + } else { + err = mOMX->use_buffer(mNode, port_index, mem, &buffer); + if (err != OK) { + LOGE("[%s] use_buffer failed with error %d", + mComponentName, err); + } + } + assert(err == OK); + + LOGV("allocated %s buffer %p.", + port_index == kPortIndexInput ? "INPUT" : "OUTPUT", + buffer); + + mBuffers.editItemAt(port_index).push_back(buffer); + mBufferMap.add(buffer, mem); + + if (port_index == kPortIndexOutput) { + OMXMediaBuffer *media_buffer = new OMXMediaBuffer(buffer, mem); + media_buffer->setObserver(this); + + mMediaBufferMap.add(buffer, media_buffer); + } + } + + LOGV("allocate %s buffers done.", + port_index == kPortIndexInput ? "INPUT" : "OUTPUT"); +} + +void OMXDecoder::onEvent( + OMX_EVENTTYPE event, OMX_U32 data1, OMX_U32 data2) { + LOGV("[%s] onEvent event=%d, data1=%ld, data2=%ld", + mComponentName, event, data1, data2); + + switch (event) { + case OMX_EventCmdComplete: { + onEventCmdComplete( + static_cast<OMX_COMMANDTYPE>(data1), data2); + + break; + } + + case OMX_EventPortSettingsChanged: { + onEventPortSettingsChanged(data1); + break; + } + + case OMX_EventBufferFlag: { + // initiateShutdown(); + break; + } + + default: + break; + } +} + +void OMXDecoder::onEventCmdComplete(OMX_COMMANDTYPE type, OMX_U32 data) { + switch (type) { + case OMX_CommandStateSet: { + OMX_STATETYPE state = static_cast<OMX_STATETYPE>(data); + onStateChanged(state); + break; + } + + case OMX_CommandPortDisable: { + OMX_U32 port_index = data; + assert(getPortStatus(port_index) == kPortStatusDisabled); + + status_t err = + mOMX->send_command(mNode, OMX_CommandPortEnable, port_index); + + allocateBuffers(port_index); + + break; + } + + case OMX_CommandPortEnable: { + OMX_U32 port_index = data; + assert(getPortStatus(port_index) ==kPortStatusDisabled); + setPortStatus(port_index, kPortStatusActive); + + assert(port_index == kPortIndexOutput); + + BufferList *obuffers = &mBuffers.editItemAt(kPortIndexOutput); + while (!obuffers->empty()) { + IOMX::buffer_id buffer = *obuffers->begin(); + obuffers->erase(obuffers->begin()); + + status_t err = mClient->fillBuffer(mNode, buffer); + assert(err == NO_ERROR); + } + + break; + } + + case OMX_CommandFlush: { + OMX_U32 port_index = data; + LOGV("Port %ld flush complete.", port_index); + assert(getPortStatus(port_index) == kPortStatusFlushing); + + setPortStatus(port_index, kPortStatusActive); + BufferList *buffers = &mBuffers.editItemAt(port_index); + while (!buffers->empty()) { + IOMX::buffer_id buffer = *buffers->begin(); + buffers->erase(buffers->begin()); + + if (port_index == kPortIndexInput) { + postEmptyBufferDone(buffer); + } else { + postInitialFillBuffer(buffer); + } + } + break; + } + + default: + break; + } +} + +void OMXDecoder::onEventPortSettingsChanged(OMX_U32 port_index) { + assert(getPortStatus(port_index) == kPortStatusActive); + setPortStatus(port_index, kPortStatusDisabled); + + status_t err = + mOMX->send_command(mNode, OMX_CommandPortDisable, port_index); + assert(err == NO_ERROR); +} + +void OMXDecoder::onStateChanged(OMX_STATETYPE to) { + if (mState == OMX_StateLoaded) { + assert(to == OMX_StateIdle); + + mState = to; + + status_t err = + mOMX->send_command(mNode, OMX_CommandStateSet, OMX_StateExecuting); + assert(err == NO_ERROR); + } else if (mState == OMX_StateIdle) { + if (to == OMX_StateExecuting) { + mState = to; + + BufferList *ibuffers = &mBuffers.editItemAt(kPortIndexInput); + while (!ibuffers->empty()) { + IOMX::buffer_id buffer = *ibuffers->begin(); + ibuffers->erase(ibuffers->begin()); + + postEmptyBufferDone(buffer); + } + + BufferList *obuffers = &mBuffers.editItemAt(kPortIndexOutput); + while (!obuffers->empty()) { + IOMX::buffer_id buffer = *obuffers->begin(); + obuffers->erase(obuffers->begin()); + + postInitialFillBuffer(buffer); + } + } else { + assert(to == OMX_StateLoaded); + + mState = to; + + setPortStatus(kPortIndexInput, kPortStatusActive); + setPortStatus(kPortIndexOutput, kPortStatusActive); + } + } else if (mState == OMX_StateExecuting) { + assert(to == OMX_StateIdle); + + mState = to; + + LOGV("Executing->Idle complete, initiating Idle->Loaded"); + status_t err = + mClient->send_command(mNode, OMX_CommandStateSet, OMX_StateLoaded); + assert(err == NO_ERROR); + + BufferList *ibuffers = &mBuffers.editItemAt(kPortIndexInput); + for (BufferList::iterator it = ibuffers->begin(); + it != ibuffers->end(); ++it) { + freeInputBuffer(*it); + } + ibuffers->clear(); + + BufferList *obuffers = &mBuffers.editItemAt(kPortIndexOutput); + for (BufferList::iterator it = obuffers->begin(); + it != obuffers->end(); ++it) { + freeOutputBuffer(*it); + } + obuffers->clear(); + } +} + +void OMXDecoder::initiateShutdown() { + Mutex::Autolock autoLock(mLock); + + if (mShutdownInitiated) { + return; + } + + if (mState == OMX_StateLoaded) { + return; + } + + assert(mState == OMX_StateExecuting); + + mShutdownInitiated = true; + + status_t err = + mClient->send_command(mNode, OMX_CommandStateSet, OMX_StateIdle); + assert(err == NO_ERROR); + + setPortStatus(kPortIndexInput, kPortStatusShutdown); + setPortStatus(kPortIndexOutput, kPortStatusShutdown); +} + +void OMXDecoder::setPortStatus(OMX_U32 port_index, PortStatus status) { + int shift = 2 * port_index; + + mPortStatusMask &= ~(3 << shift); + mPortStatusMask |= status << shift; +} + +OMXDecoder::PortStatus OMXDecoder::getPortStatus( + OMX_U32 port_index) const { + int shift = 2 * port_index; + + return static_cast<PortStatus>((mPortStatusMask >> shift) & 3); +} + +void OMXDecoder::onEmptyBufferDone(IOMX::buffer_id buffer) { + LOGV("[%s] onEmptyBufferDone (%p)", mComponentName, buffer); + + status_t err; + switch (getPortStatus(kPortIndexInput)) { + case kPortStatusDisabled: + freeInputBuffer(buffer); + err = NO_ERROR; + break; + + case kPortStatusShutdown: + LOGV("We're shutting down, enqueue INPUT buffer %p.", buffer); + mBuffers.editItemAt(kPortIndexInput).push_back(buffer); + err = NO_ERROR; + break; + + case kPortStatusFlushing: + LOGV("We're currently flushing, enqueue INPUT buffer %p.", buffer); + mBuffers.editItemAt(kPortIndexInput).push_back(buffer); + err = NO_ERROR; + break; + + default: + onRealEmptyBufferDone(buffer); + err = NO_ERROR; + break; + } + assert(err == NO_ERROR); +} + +void OMXDecoder::onFillBufferDone(const omx_message &msg) { + IOMX::buffer_id buffer = msg.u.extended_buffer_data.buffer; + + LOGV("[%s] onFillBufferDone (%p)", mComponentName, buffer); + + status_t err; + switch (getPortStatus(kPortIndexOutput)) { + case kPortStatusDisabled: + freeOutputBuffer(buffer); + err = NO_ERROR; + break; + case kPortStatusShutdown: + LOGV("We're shutting down, enqueue OUTPUT buffer %p.", buffer); + mBuffers.editItemAt(kPortIndexOutput).push_back(buffer); + err = NO_ERROR; + break; + + case kPortStatusFlushing: + LOGV("We're currently flushing, enqueue OUTPUT buffer %p.", buffer); + mBuffers.editItemAt(kPortIndexOutput).push_back(buffer); + err = NO_ERROR; + break; + + default: + { + if (msg.type == omx_message::INITIAL_FILL_BUFFER) { + err = mClient->fillBuffer(mNode, buffer); + } else { + LOGV("[%s] Filled OUTPUT buffer %p, flags=0x%08lx.", + mComponentName, buffer, msg.u.extended_buffer_data.flags); + + onRealFillBufferDone(msg); + err = NO_ERROR; + } + break; + } + } + assert(err == NO_ERROR); +} + +void OMXDecoder::onRealEmptyBufferDone(IOMX::buffer_id buffer) { + if (mReachedEndOfInput) { + // We already sent the EOS notification. + + mBuffers.editItemAt(kPortIndexInput).push_back(buffer); + return; + } + + const sp<IMemory> mem = mBufferMap.valueFor(buffer); + assert(mem.get() != NULL); + + static const uint8_t kNALStartCode[4] = { 0x00, 0x00, 0x00, 0x01 }; + + if (mCodecSpecificDataIterator != mCodecSpecificData.end()) { + List<CodecSpecificData>::iterator it = mCodecSpecificDataIterator; + + size_t range_length = 0; + + if (!strcmp(mComponentName, "OMX.qcom.video.decoder.avc")) { + assert((*mCodecSpecificDataIterator).size + 4 <= mem->size()); + + memcpy(mem->pointer(), kNALStartCode, 4); + + memcpy((uint8_t *)mem->pointer() + 4, (*it).data, (*it).size); + range_length = (*it).size + 4; + } else { + assert((*mCodecSpecificDataIterator).size <= mem->size()); + + memcpy((uint8_t *)mem->pointer(), (*it).data, (*it).size); + range_length = (*it).size; + } + + ++mCodecSpecificDataIterator; + + status_t err = mClient->emptyBuffer( + mNode, buffer, 0, range_length, + OMX_BUFFERFLAG_ENDOFFRAME | OMX_BUFFERFLAG_CODECCONFIG, + 0); + + assert(err == NO_ERROR); + + return; + } + + LOGV("[%s] waiting for input data", mComponentName); + + MediaBuffer *input_buffer; + for (;;) { + status_t err; + + if (mSeeking) { + MediaSource::ReadOptions options; + options.setSeekTo(mSeekTimeUs); + + mSeeking = false; + + err = mSource->read(&input_buffer, &options); + } else { + err = mSource->read(&input_buffer); + } + assert((err == OK && input_buffer != NULL) + || (err != OK && input_buffer == NULL)); + + if (err == ERROR_END_OF_STREAM) { + LOGE("[%s] Reached end of stream.", mComponentName); + mReachedEndOfInput = true; + } else { + LOGV("[%s] got input data", mComponentName); + } + + if (err != OK) { + status_t err2 = mClient->emptyBuffer( + mNode, buffer, 0, 0, OMX_BUFFERFLAG_EOS, 0); + + assert(err2 == NO_ERROR); + return; + } + + if (mSeeking) { + input_buffer->release(); + input_buffer = NULL; + + continue; + } + + break; + } + + const uint8_t *src_data = + (const uint8_t *)input_buffer->data() + input_buffer->range_offset(); + + size_t src_length = input_buffer->range_length(); + if (src_length == 195840) { + // When feeding the output of the AVC decoder into the H263 encoder, + // buffer sizes mismatch if width % 16 != 0 || height % 16 != 0. + src_length = 194400; // XXX HACK + } else if (src_length == 115200) { + src_length = 114240; // XXX HACK + } + + if (src_length > mem->size()) { + LOGE("src_length=%d > mem->size() = %d\n", + src_length, mem->size()); + } + + assert(src_length <= mem->size()); + memcpy(mem->pointer(), src_data, src_length); + + OMX_U32 flags = 0; + if (!mIsMP3) { + // Only mp3 audio data may be streamed, all other data is assumed + // to be fed into the decoder at frame boundaries. + flags |= OMX_BUFFERFLAG_ENDOFFRAME; + } + + int32_t units, scale; + bool success = + input_buffer->meta_data()->findInt32(kKeyTimeUnits, &units); + + success = success && + input_buffer->meta_data()->findInt32(kKeyTimeScale, &scale); + + OMX_TICKS timestamp = 0; + + if (success) { + // XXX units should be microseconds but PV treats them as milliseconds. + timestamp = ((OMX_S64)units * 1000) / scale; + } + + input_buffer->release(); + input_buffer = NULL; + + LOGV("[%s] Calling EmptyBuffer on buffer %p", + mComponentName, buffer); + + status_t err2 = mClient->emptyBuffer( + mNode, buffer, 0, src_length, flags, timestamp); + assert(err2 == OK); +} + +void OMXDecoder::onRealFillBufferDone(const omx_message &msg) { + OMXMediaBuffer *media_buffer = + mMediaBufferMap.valueFor(msg.u.extended_buffer_data.buffer); + + media_buffer->set_range( + msg.u.extended_buffer_data.range_offset, + msg.u.extended_buffer_data.range_length); + + media_buffer->add_ref(); + + media_buffer->meta_data()->clear(); + + media_buffer->meta_data()->setInt32( + kKeyTimeUnits, msg.u.extended_buffer_data.timestamp); + media_buffer->meta_data()->setInt32(kKeyTimeScale, 1000); + + if (msg.u.extended_buffer_data.flags & OMX_BUFFERFLAG_SYNCFRAME) { + media_buffer->meta_data()->setInt32(kKeyIsSyncFrame, true); + } + + media_buffer->meta_data()->setPointer( + kKeyPlatformPrivate, + msg.u.extended_buffer_data.platform_private); + + if (msg.u.extended_buffer_data.flags & OMX_BUFFERFLAG_EOS) { + mErrorCondition = ERROR_END_OF_STREAM; + } + + mOutputBuffers.push_back(media_buffer); + mOutputBufferAvailable.signal(); +} + +void OMXDecoder::signalBufferReturned(MediaBuffer *_buffer) { + Mutex::Autolock autoLock(mLock); + + OMXMediaBuffer *media_buffer = static_cast<OMXMediaBuffer *>(_buffer); + + IOMX::buffer_id buffer = media_buffer->buffer_id(); + + PortStatus outputStatus = getPortStatus(kPortIndexOutput); + if (outputStatus == kPortStatusShutdown + || outputStatus == kPortStatusFlushing) { + mBuffers.editItemAt(kPortIndexOutput).push_back(buffer); + } else { + LOGV("[%s] Calling FillBuffer on buffer %p.", mComponentName, buffer); + + status_t err = mClient->fillBuffer(mNode, buffer); + assert(err == NO_ERROR); + } +} + +void OMXDecoder::freeInputBuffer(IOMX::buffer_id buffer) { + LOGV("freeInputBuffer %p", buffer); + + status_t err = mOMX->free_buffer(mNode, kPortIndexInput, buffer); + assert(err == NO_ERROR); + mBufferMap.removeItem(buffer); + + LOGV("freeInputBuffer %p done", buffer); +} + +void OMXDecoder::freeOutputBuffer(IOMX::buffer_id buffer) { + LOGV("freeOutputBuffer %p", buffer); + + status_t err = mOMX->free_buffer(mNode, kPortIndexOutput, buffer); + assert(err == NO_ERROR); + mBufferMap.removeItem(buffer); + + ssize_t index = mMediaBufferMap.indexOfKey(buffer); + assert(index >= 0); + MediaBuffer *mbuffer = mMediaBufferMap.editValueAt(index); + mMediaBufferMap.removeItemsAt(index); + mbuffer->setObserver(NULL); + mbuffer->release(); + mbuffer = NULL; + + LOGV("freeOutputBuffer %p done", buffer); +} + +void OMXDecoder::dumpPortDefinition(OMX_U32 port_index) { + OMX_PARAM_PORTDEFINITIONTYPE def; + def.nSize = sizeof(def); + def.nVersion.s.nVersionMajor = 1; + def.nVersion.s.nVersionMinor = 1; + def.nPortIndex = port_index; + + status_t err = mOMX->get_parameter( + mNode, OMX_IndexParamPortDefinition, &def, sizeof(def)); + assert(err == NO_ERROR); + + LOGI("DumpPortDefinition on port %ld", port_index); + LOGI("nBufferCountActual = %ld, nBufferCountMin = %ld, nBufferSize = %ld", + def.nBufferCountActual, def.nBufferCountMin, def.nBufferSize); + switch (def.eDomain) { + case OMX_PortDomainAudio: + { + LOGI("eDomain = AUDIO"); + + if (port_index == kPortIndexOutput) { + OMX_AUDIO_PORTDEFINITIONTYPE *audio_def = &def.format.audio; + assert(audio_def->eEncoding == OMX_AUDIO_CodingPCM); + + OMX_AUDIO_PARAM_PCMMODETYPE params; + params.nSize = sizeof(params); + params.nVersion.s.nVersionMajor = 1; + params.nVersion.s.nVersionMinor = 1; + params.nPortIndex = port_index; + + err = mOMX->get_parameter( + mNode, OMX_IndexParamAudioPcm, ¶ms, sizeof(params)); + assert(err == OK); + + assert(params.nChannels == 1 || params.bInterleaved); + assert(params.eNumData == OMX_NumericalDataSigned); + assert(params.nBitPerSample == 16); + assert(params.ePCMMode == OMX_AUDIO_PCMModeLinear); + + LOGI("nChannels = %ld, nSamplingRate = %ld", + params.nChannels, params.nSamplingRate); + } + + break; + } + + case OMX_PortDomainVideo: + { + LOGI("eDomain = VIDEO"); + + OMX_VIDEO_PORTDEFINITIONTYPE *video_def = &def.format.video; + LOGI("nFrameWidth = %ld, nFrameHeight = %ld, nStride = %ld, " + "nSliceHeight = %ld", + video_def->nFrameWidth, video_def->nFrameHeight, + video_def->nStride, video_def->nSliceHeight); + LOGI("nBitrate = %ld, xFrameRate = %.2f", + video_def->nBitrate, video_def->xFramerate / 65536.0f); + LOGI("eCompressionFormat = %d, eColorFormat = %d", + video_def->eCompressionFormat, video_def->eColorFormat); + + break; + } + + default: + LOGI("eDomain = UNKNOWN"); + break; + } +} + +void OMXDecoder::postStart() { + omx_message msg; + msg.type = omx_message::START; + postMessage(msg); +} + +void OMXDecoder::postEmptyBufferDone(IOMX::buffer_id buffer) { + omx_message msg; + msg.type = omx_message::EMPTY_BUFFER_DONE; + msg.u.buffer_data.node = mNode; + msg.u.buffer_data.buffer = buffer; + postMessage(msg); +} + +void OMXDecoder::postInitialFillBuffer(IOMX::buffer_id buffer) { + omx_message msg; + msg.type = omx_message::INITIAL_FILL_BUFFER; + msg.u.buffer_data.node = mNode; + msg.u.buffer_data.buffer = buffer; + postMessage(msg); +} + +} // namespace android diff --git a/media/libstagefright/QComHardwareRenderer.cpp b/media/libstagefright/QComHardwareRenderer.cpp new file mode 100644 index 0000000..5a23792 --- /dev/null +++ b/media/libstagefright/QComHardwareRenderer.cpp @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2009 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. + */ + +#undef NDEBUG +#include <assert.h> + +#include <binder/MemoryHeapBase.h> +#include <binder/MemoryHeapPmem.h> +#include <media/stagefright/QComHardwareRenderer.h> +#include <ui/ISurface.h> + +namespace android { + +//////////////////////////////////////////////////////////////////////////////// + +typedef struct PLATFORM_PRIVATE_ENTRY +{ + /* Entry type */ + uint32_t type; + + /* Pointer to platform specific entry */ + void *entry; + +} PLATFORM_PRIVATE_ENTRY; + +typedef struct PLATFORM_PRIVATE_LIST +{ + /* Number of entries */ + uint32_t nEntries; + + /* Pointer to array of platform specific entries * + * Contiguous block of PLATFORM_PRIVATE_ENTRY elements */ + PLATFORM_PRIVATE_ENTRY *entryList; + +} PLATFORM_PRIVATE_LIST; + +// data structures for tunneling buffers +typedef struct PLATFORM_PRIVATE_PMEM_INFO +{ + /* pmem file descriptor */ + uint32_t pmem_fd; + uint32_t offset; + +} PLATFORM_PRIVATE_PMEM_INFO; + +#define PLATFORM_PRIVATE_PMEM 1 + +QComHardwareRenderer::QComHardwareRenderer( + const sp<ISurface> &surface, + size_t displayWidth, size_t displayHeight, + size_t decodedWidth, size_t decodedHeight) + : mISurface(surface), + mDisplayWidth(displayWidth), + mDisplayHeight(displayHeight), + mDecodedWidth(decodedWidth), + mDecodedHeight(decodedHeight), + mFrameSize((mDecodedWidth * mDecodedHeight * 3) / 2) { + assert(mISurface.get() != NULL); + assert(mDecodedWidth > 0); + assert(mDecodedHeight > 0); +} + +QComHardwareRenderer::~QComHardwareRenderer() { + mISurface->unregisterBuffers(); +} + +void QComHardwareRenderer::render( + const void *data, size_t size, void *platformPrivate) { + size_t offset; + if (!getOffset(platformPrivate, &offset)) { + return; + } + + mISurface->postBuffer(offset); +} + +bool QComHardwareRenderer::getOffset(void *platformPrivate, size_t *offset) { + *offset = 0; + + PLATFORM_PRIVATE_LIST *list = (PLATFORM_PRIVATE_LIST *)platformPrivate; + for (uint32_t i = 0; i < list->nEntries; ++i) { + if (list->entryList[i].type != PLATFORM_PRIVATE_PMEM) { + continue; + } + + PLATFORM_PRIVATE_PMEM_INFO *info = + (PLATFORM_PRIVATE_PMEM_INFO *)list->entryList[i].entry; + + if (info != NULL) { + if (mMemoryHeap.get() == NULL) { + publishBuffers(info->pmem_fd); + } + + if (mMemoryHeap.get() == NULL) { + return false; + } + + *offset = info->offset; + + return true; + } + } + + return false; +} + +void QComHardwareRenderer::publishBuffers(uint32_t pmem_fd) { + sp<MemoryHeapBase> master = + reinterpret_cast<MemoryHeapBase *>(pmem_fd); + + master->setDevice("/dev/pmem"); + + mMemoryHeap = new MemoryHeapPmem(master, 0); + mMemoryHeap->slap(); + + ISurface::BufferHeap bufferHeap( + mDisplayWidth, mDisplayHeight, + mDecodedWidth, mDecodedHeight, + PIXEL_FORMAT_YCbCr_420_SP, + mMemoryHeap); + + status_t err = mISurface->registerBuffers(bufferHeap); + assert(err == OK); +} + +} // namespace android diff --git a/media/libstagefright/SampleTable.cpp b/media/libstagefright/SampleTable.cpp new file mode 100644 index 0000000..8f1fa67 --- /dev/null +++ b/media/libstagefright/SampleTable.cpp @@ -0,0 +1,598 @@ +/* + * Copyright (C) 2009 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_TAG "SampleTable" +#include <utils/Log.h> + +#include <arpa/inet.h> +#include <assert.h> + +#include <media/stagefright/DataSource.h> +#include <media/stagefright/SampleTable.h> +#include <media/stagefright/Utils.h> + +namespace android { + +static const uint32_t kChunkOffsetType32 = FOURCC('s', 't', 'c', 'o'); +static const uint32_t kChunkOffsetType64 = FOURCC('c', 'o', '6', '4'); +static const uint32_t kSampleSizeType32 = FOURCC('s', 't', 's', 'z'); +static const uint32_t kSampleSizeTypeCompact = FOURCC('s', 't', 'z', '2'); + +SampleTable::SampleTable(DataSource *source) + : mDataSource(source), + mChunkOffsetOffset(-1), + mChunkOffsetType(0), + mNumChunkOffsets(0), + mSampleToChunkOffset(-1), + mNumSampleToChunkOffsets(0), + mSampleSizeOffset(-1), + mSampleSizeFieldSize(0), + mDefaultSampleSize(0), + mNumSampleSizes(0), + mTimeToSampleCount(0), + mTimeToSample(NULL), + mSyncSampleOffset(-1), + mNumSyncSamples(0) { +} + +SampleTable::~SampleTable() { + delete[] mTimeToSample; + mTimeToSample = NULL; +} + +status_t SampleTable::setChunkOffsetParams( + uint32_t type, off_t data_offset, off_t data_size) { + if (mChunkOffsetOffset >= 0) { + return ERROR_MALFORMED; + } + + assert(type == kChunkOffsetType32 || type == kChunkOffsetType64); + + mChunkOffsetOffset = data_offset; + mChunkOffsetType = type; + + if (data_size < 8) { + return ERROR_MALFORMED; + } + + uint8_t header[8]; + if (mDataSource->read_at( + data_offset, header, sizeof(header)) < (ssize_t)sizeof(header)) { + return ERROR_IO; + } + + if (U32_AT(header) != 0) { + // Expected version = 0, flags = 0. + return ERROR_MALFORMED; + } + + mNumChunkOffsets = U32_AT(&header[4]); + + if (mChunkOffsetType == kChunkOffsetType32) { + if (data_size < 8 + mNumChunkOffsets * 4) { + return ERROR_MALFORMED; + } + } else { + if (data_size < 8 + mNumChunkOffsets * 8) { + return ERROR_MALFORMED; + } + } + + return OK; +} + +status_t SampleTable::setSampleToChunkParams( + off_t data_offset, off_t data_size) { + if (mSampleToChunkOffset >= 0) { + return ERROR_MALFORMED; + } + + mSampleToChunkOffset = data_offset; + + if (data_size < 8) { + return ERROR_MALFORMED; + } + + uint8_t header[8]; + if (mDataSource->read_at( + data_offset, header, sizeof(header)) < (ssize_t)sizeof(header)) { + return ERROR_IO; + } + + if (U32_AT(header) != 0) { + // Expected version = 0, flags = 0. + return ERROR_MALFORMED; + } + + mNumSampleToChunkOffsets = U32_AT(&header[4]); + + if (data_size < 8 + mNumSampleToChunkOffsets * 12) { + return ERROR_MALFORMED; + } + + return OK; +} + +status_t SampleTable::setSampleSizeParams( + uint32_t type, off_t data_offset, off_t data_size) { + if (mSampleSizeOffset >= 0) { + return ERROR_MALFORMED; + } + + assert(type == kSampleSizeType32 || type == kSampleSizeTypeCompact); + + mSampleSizeOffset = data_offset; + + if (data_size < 12) { + return ERROR_MALFORMED; + } + + uint8_t header[12]; + if (mDataSource->read_at( + data_offset, header, sizeof(header)) < (ssize_t)sizeof(header)) { + return ERROR_IO; + } + + if (U32_AT(header) != 0) { + // Expected version = 0, flags = 0. + return ERROR_MALFORMED; + } + + mDefaultSampleSize = U32_AT(&header[4]); + mNumSampleSizes = U32_AT(&header[8]); + + if (type == kSampleSizeType32) { + mSampleSizeFieldSize = 32; + + if (mDefaultSampleSize != 0) { + return OK; + } + + if (data_size < 12 + mNumSampleSizes * 4) { + return ERROR_MALFORMED; + } + } else { + if ((mDefaultSampleSize & 0xffffff00) != 0) { + // The high 24 bits are reserved and must be 0. + return ERROR_MALFORMED; + } + + mSampleSizeFieldSize = mDefaultSampleSize & 0xf; + mDefaultSampleSize = 0; + + if (mSampleSizeFieldSize != 4 && mSampleSizeFieldSize != 8 + && mSampleSizeFieldSize != 16) { + return ERROR_MALFORMED; + } + + if (data_size < 12 + (mNumSampleSizes * mSampleSizeFieldSize + 4) / 8) { + return ERROR_MALFORMED; + } + } + + return OK; +} + +status_t SampleTable::setTimeToSampleParams( + off_t data_offset, off_t data_size) { + if (mTimeToSample != NULL || data_size < 8) { + return ERROR_MALFORMED; + } + + uint8_t header[8]; + if (mDataSource->read_at( + data_offset, header, sizeof(header)) < (ssize_t)sizeof(header)) { + return ERROR_IO; + } + + if (U32_AT(header) != 0) { + // Expected version = 0, flags = 0. + return ERROR_MALFORMED; + } + + mTimeToSampleCount = U32_AT(&header[4]); + mTimeToSample = new uint32_t[mTimeToSampleCount * 2]; + + size_t size = sizeof(uint32_t) * mTimeToSampleCount * 2; + if (mDataSource->read_at( + data_offset + 8, mTimeToSample, size) < (ssize_t)size) { + return ERROR_IO; + } + + for (uint32_t i = 0; i < mTimeToSampleCount * 2; ++i) { + mTimeToSample[i] = ntohl(mTimeToSample[i]); + } + + return OK; +} + +status_t SampleTable::setSyncSampleParams(off_t data_offset, off_t data_size) { + if (mSyncSampleOffset >= 0 || data_size < 8) { + return ERROR_MALFORMED; + } + + mSyncSampleOffset = data_offset; + + uint8_t header[8]; + if (mDataSource->read_at( + data_offset, header, sizeof(header)) < (ssize_t)sizeof(header)) { + return ERROR_IO; + } + + if (U32_AT(header) != 0) { + // Expected version = 0, flags = 0. + return ERROR_MALFORMED; + } + + mNumSyncSamples = U32_AT(&header[4]); + + if (mNumSyncSamples < 2) { + LOGW("Table of sync samples is empty or has only a single entry!"); + } + return OK; +} + +uint32_t SampleTable::countChunkOffsets() const { + return mNumChunkOffsets; +} + +status_t SampleTable::getChunkOffset(uint32_t chunk_index, off_t *offset) { + *offset = 0; + + if (mChunkOffsetOffset < 0) { + return ERROR_MALFORMED; + } + + if (chunk_index >= mNumChunkOffsets) { + return ERROR_OUT_OF_RANGE; + } + + if (mChunkOffsetType == kChunkOffsetType32) { + uint32_t offset32; + + if (mDataSource->read_at( + mChunkOffsetOffset + 8 + 4 * chunk_index, + &offset32, + sizeof(offset32)) < (ssize_t)sizeof(offset32)) { + return ERROR_IO; + } + + *offset = ntohl(offset32); + } else { + assert(mChunkOffsetOffset == kChunkOffsetType64); + + uint64_t offset64; + if (mDataSource->read_at( + mChunkOffsetOffset + 8 + 8 * chunk_index, + &offset64, + sizeof(offset64)) < (ssize_t)sizeof(offset64)) { + return ERROR_IO; + } + + *offset = ntoh64(offset64); + } + + return OK; +} + +status_t SampleTable::getChunkForSample( + uint32_t sample_index, + uint32_t *chunk_index, + uint32_t *chunk_relative_sample_index, + uint32_t *desc_index) { + *chunk_index = 0; + *chunk_relative_sample_index = 0; + *desc_index = 0; + + if (mSampleToChunkOffset < 0) { + return ERROR_MALFORMED; + } + + if (sample_index >= countSamples()) { + return ERROR_END_OF_STREAM; + } + + uint32_t first_chunk = 0; + uint32_t samples_per_chunk = 0; + uint32_t chunk_desc_index = 0; + + uint32_t index = 0; + while (index < mNumSampleToChunkOffsets) { + uint8_t buffer[12]; + if (mDataSource->read_at(mSampleToChunkOffset + 8 + index * 12, + buffer, sizeof(buffer)) < (ssize_t)sizeof(buffer)) { + return ERROR_IO; + } + + uint32_t stop_chunk = U32_AT(buffer); + if (sample_index < (stop_chunk - first_chunk) * samples_per_chunk) { + break; + } + + sample_index -= (stop_chunk - first_chunk) * samples_per_chunk; + first_chunk = stop_chunk; + samples_per_chunk = U32_AT(&buffer[4]); + chunk_desc_index = U32_AT(&buffer[8]); + + ++index; + } + + *chunk_index = sample_index / samples_per_chunk + first_chunk - 1; + *chunk_relative_sample_index = sample_index % samples_per_chunk; + *desc_index = chunk_desc_index; + + return OK; +} + +uint32_t SampleTable::countSamples() const { + return mNumSampleSizes; +} + +status_t SampleTable::getSampleSize( + uint32_t sample_index, size_t *sample_size) { + *sample_size = 0; + + if (mSampleSizeOffset < 0) { + return ERROR_MALFORMED; + } + + if (sample_index >= mNumSampleSizes) { + return ERROR_OUT_OF_RANGE; + } + + if (mDefaultSampleSize > 0) { + *sample_size = mDefaultSampleSize; + return OK; + } + + switch (mSampleSizeFieldSize) { + case 32: + { + if (mDataSource->read_at( + mSampleSizeOffset + 12 + 4 * sample_index, + sample_size, sizeof(*sample_size)) < (ssize_t)sizeof(*sample_size)) { + return ERROR_IO; + } + + *sample_size = ntohl(*sample_size); + break; + } + + case 16: + { + uint16_t x; + if (mDataSource->read_at( + mSampleSizeOffset + 12 + 2 * sample_index, + &x, sizeof(x)) < (ssize_t)sizeof(x)) { + return ERROR_IO; + } + + *sample_size = ntohs(x); + break; + } + + case 8: + { + uint8_t x; + if (mDataSource->read_at( + mSampleSizeOffset + 12 + sample_index, + &x, sizeof(x)) < (ssize_t)sizeof(x)) { + return ERROR_IO; + } + + *sample_size = x; + break; + } + + default: + { + assert(mSampleSizeFieldSize == 4); + + uint8_t x; + if (mDataSource->read_at( + mSampleSizeOffset + 12 + sample_index / 2, + &x, sizeof(x)) < (ssize_t)sizeof(x)) { + return ERROR_IO; + } + + *sample_size = (sample_index & 1) ? x & 0x0f : x >> 4; + break; + } + } + + return OK; +} + +status_t SampleTable::getSampleOffsetAndSize( + uint32_t sample_index, off_t *offset, size_t *size) { + Mutex::Autolock autoLock(mLock); + + *offset = 0; + *size = 0; + + uint32_t chunk_index; + uint32_t chunk_relative_sample_index; + uint32_t desc_index; + status_t err = getChunkForSample( + sample_index, &chunk_index, &chunk_relative_sample_index, + &desc_index); + + if (err != OK) { + return err; + } + + err = getChunkOffset(chunk_index, offset); + + if (err != OK) { + return err; + } + + for (uint32_t j = 0; j < chunk_relative_sample_index; ++j) { + size_t sample_size; + err = getSampleSize(sample_index - j - 1, &sample_size); + + if (err != OK) { + return err; + } + + *offset += sample_size; + } + + err = getSampleSize(sample_index, size); + + if (err != OK) { + return err; + } + + return OK; +} + +status_t SampleTable::getMaxSampleSize(size_t *max_size) { + Mutex::Autolock autoLock(mLock); + + *max_size = 0; + + for (uint32_t i = 0; i < mNumSampleSizes; ++i) { + size_t sample_size; + status_t err = getSampleSize(i, &sample_size); + + if (err != OK) { + return err; + } + + if (sample_size > *max_size) { + *max_size = sample_size; + } + } + + return OK; +} + +status_t SampleTable::getDecodingTime(uint32_t sample_index, uint32_t *time) { + // XXX FIXME idiotic (for the common use-case) O(n) algorithm below... + + Mutex::Autolock autoLock(mLock); + + if (sample_index >= mNumSampleSizes) { + return ERROR_OUT_OF_RANGE; + } + + uint32_t cur_sample = 0; + *time = 0; + for (uint32_t i = 0; i < mTimeToSampleCount; ++i) { + uint32_t n = mTimeToSample[2 * i]; + uint32_t delta = mTimeToSample[2 * i + 1]; + + if (sample_index < cur_sample + n) { + *time += delta * (sample_index - cur_sample); + + return OK; + } + + *time += delta * n; + cur_sample += n; + } + + return ERROR_OUT_OF_RANGE; +} + +status_t SampleTable::findClosestSample( + uint32_t req_time, uint32_t *sample_index, uint32_t flags) { + Mutex::Autolock autoLock(mLock); + + uint32_t cur_sample = 0; + uint32_t time = 0; + for (uint32_t i = 0; i < mTimeToSampleCount; ++i) { + uint32_t n = mTimeToSample[2 * i]; + uint32_t delta = mTimeToSample[2 * i + 1]; + + if (req_time < time + n * delta) { + int j = (req_time - time) / delta; + + *sample_index = cur_sample + j; + + if (flags & kSyncSample_Flag) { + return findClosestSyncSample(*sample_index, sample_index); + } + + return OK; + } + + time += delta * n; + cur_sample += n; + } + + return ERROR_OUT_OF_RANGE; +} + +status_t SampleTable::findClosestSyncSample( + uint32_t start_sample_index, uint32_t *sample_index) { + *sample_index = 0; + + if (mSyncSampleOffset < 0) { + // All samples are sync-samples. + *sample_index = start_sample_index; + return OK; + } + + uint32_t x; + uint32_t left = 0; + uint32_t right = mNumSyncSamples; + while (left < right) { + uint32_t mid = (left + right) / 2; + if (mDataSource->read_at( + mSyncSampleOffset + 8 + (mid - 1) * 4, &x, 4) != 4) { + return ERROR_IO; + } + + x = ntohl(x); + + if (x < (start_sample_index + 1)) { + left = mid + 1; + } else if (x > (start_sample_index + 1)) { + right = mid; + } else { + break; + } + } + +#if 1 + // Make sure we return a sample at or _after_ the requested one. + // Seeking to a particular time in a media source containing audio and + // video will most likely be able to sync fairly close to the requested + // time in the audio track but may only be able to seek a fair distance + // from the requested time in the video track. + // If we seek the video track to a time earlier than the audio track, + // we'll cause the video track to be late for quite a while, the decoder + // trying to catch up. + // If we seek the video track to a time later than the audio track, + // audio will start playing fine while no video will be output for a + // while, the video decoder will not stress the system. + if (mDataSource->read_at( + mSyncSampleOffset + 8 + (left - 1) * 4, &x, 4) != 4) { + return ERROR_IO; + } + x = ntohl(x); + assert((x - 1) >= start_sample_index); +#endif + + *sample_index = x - 1; + + return OK; +} + +} // namespace android + diff --git a/media/libstagefright/ShoutcastSource.cpp b/media/libstagefright/ShoutcastSource.cpp new file mode 100644 index 0000000..17b626e --- /dev/null +++ b/media/libstagefright/ShoutcastSource.cpp @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2009 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. + */ + +#include <stdlib.h> + +#include <media/stagefright/HTTPStream.h> +#include <media/stagefright/MediaBuffer.h> +#include <media/stagefright/MediaBufferGroup.h> +#include <media/stagefright/MetaData.h> +#include <media/stagefright/ShoutcastSource.h> +#include <media/stagefright/string.h> + +namespace android { + +ShoutcastSource::ShoutcastSource(HTTPStream *http) + : mHttp(http), + mMetaDataOffset(0), + mBytesUntilMetaData(0), + mGroup(NULL), + mStarted(false) { + string metaint; + if (mHttp->find_header_value("icy-metaint", &metaint)) { + char *end; + const char *start = metaint.c_str(); + mMetaDataOffset = strtol(start, &end, 10); + assert(end > start && *end == '\0'); + assert(mMetaDataOffset > 0); + + mBytesUntilMetaData = mMetaDataOffset; + } +} + +ShoutcastSource::~ShoutcastSource() { + if (mStarted) { + stop(); + } + + delete mHttp; + mHttp = NULL; +} + +status_t ShoutcastSource::start(MetaData *) { + assert(!mStarted); + + mGroup = new MediaBufferGroup; + mGroup->add_buffer(new MediaBuffer(4096)); // XXX + + mStarted = true; + + return OK; +} + +status_t ShoutcastSource::stop() { + assert(mStarted); + + delete mGroup; + mGroup = NULL; + + mStarted = false; + + return OK; +} + +sp<MetaData> ShoutcastSource::getFormat() { + sp<MetaData> meta = new MetaData; + meta->setCString(kKeyMIMEType, "audio/mpeg"); + meta->setInt32(kKeySampleRate, 44100); + meta->setInt32(kKeyChannelCount, 2); // XXX + + return meta; +} + +status_t ShoutcastSource::read( + MediaBuffer **out, const ReadOptions *options) { + assert(mStarted); + + *out = NULL; + + int64_t seekTimeUs; + if (options && options->getSeekTo(&seekTimeUs)) { + return ERROR_UNSUPPORTED; + } + + MediaBuffer *buffer; + status_t err = mGroup->acquire_buffer(&buffer); + if (err != OK) { + return err; + } + + *out = buffer; + + size_t num_bytes = buffer->size(); + if (mMetaDataOffset > 0 && num_bytes > mBytesUntilMetaData) { + num_bytes = mBytesUntilMetaData; + } + + ssize_t n = mHttp->receive(buffer->data(), num_bytes); + + if (n <= 0) { + return (status_t)n; + } + + buffer->set_range(0, n); + + mBytesUntilMetaData -= (size_t)n; + + if (mBytesUntilMetaData == 0) { + unsigned char num_16_byte_blocks = 0; + n = mHttp->receive((char *)&num_16_byte_blocks, 1); + assert(n == 1); + + char meta[255 * 16]; + size_t meta_size = num_16_byte_blocks * 16; + size_t meta_length = 0; + while (meta_length < meta_size) { + n = mHttp->receive(&meta[meta_length], meta_size - meta_length); + if (n <= 0) { + return (status_t)n; + } + + meta_length += (size_t) n; + } + + while (meta_length > 0 && meta[meta_length - 1] == '\0') { + --meta_length; + } + + if (meta_length > 0) { + // Technically we should probably attach this meta data to the + // next buffer. XXX + buffer->meta_data()->setData('shou', 'shou', meta, meta_length); + } + + mBytesUntilMetaData = mMetaDataOffset; + } + + return OK; +} + +} // namespace android + diff --git a/media/libstagefright/SoftwareRenderer.cpp b/media/libstagefright/SoftwareRenderer.cpp new file mode 100644 index 0000000..66b6b07 --- /dev/null +++ b/media/libstagefright/SoftwareRenderer.cpp @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2009 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_TAG "SoftwareRenderer" +#include <utils/Log.h> + +#undef NDEBUG +#include <assert.h> + +#include <binder/MemoryHeapBase.h> +#include <media/stagefright/SoftwareRenderer.h> +#include <ui/ISurface.h> + +namespace android { + +#define QCOM_YUV 0 + +SoftwareRenderer::SoftwareRenderer( + const sp<ISurface> &surface, + size_t displayWidth, size_t displayHeight, + size_t decodedWidth, size_t decodedHeight) + : mISurface(surface), + mDisplayWidth(displayWidth), + mDisplayHeight(displayHeight), + mDecodedWidth(decodedWidth), + mDecodedHeight(decodedHeight), + mFrameSize(mDecodedWidth * mDecodedHeight * 2), // RGB565 + mMemoryHeap(new MemoryHeapBase(2 * mFrameSize)), + mIndex(0) { + assert(mISurface.get() != NULL); + assert(mDecodedWidth > 0); + assert(mDecodedHeight > 0); + assert(mMemoryHeap->heapID() >= 0); + + ISurface::BufferHeap bufferHeap( + mDisplayWidth, mDisplayHeight, + mDecodedWidth, mDecodedHeight, + PIXEL_FORMAT_RGB_565, + mMemoryHeap); + + status_t err = mISurface->registerBuffers(bufferHeap); + assert(err == OK); +} + +SoftwareRenderer::~SoftwareRenderer() { + mISurface->unregisterBuffers(); +} + +void SoftwareRenderer::render( + const void *data, size_t size, void *platformPrivate) { + assert(size >= (mDecodedWidth * mDecodedHeight * 3) / 2); + + static const signed kClipMin = -278; + static const signed kClipMax = 535; + static uint8_t kClip[kClipMax - kClipMin + 1]; + static uint8_t *kAdjustedClip = &kClip[-kClipMin]; + + static bool clipInitialized = false; + + if (!clipInitialized) { + for (signed i = kClipMin; i <= kClipMax; ++i) { + kClip[i - kClipMin] = (i < 0) ? 0 : (i > 255) ? 255 : (uint8_t)i; + } + clipInitialized = true; + } + + size_t offset = mIndex * mFrameSize; + + void *dst = (uint8_t *)mMemoryHeap->getBase() + offset; + + uint32_t *dst_ptr = (uint32_t *)dst; + + const uint8_t *src_y = (const uint8_t *)data; + + const uint8_t *src_u = + (const uint8_t *)src_y + mDecodedWidth * mDecodedHeight; + +#if !QCOM_YUV + const uint8_t *src_v = + (const uint8_t *)src_u + (mDecodedWidth / 2) * (mDecodedHeight / 2); +#endif + + for (size_t y = 0; y < mDecodedHeight; ++y) { + for (size_t x = 0; x < mDecodedWidth; x += 2) { + // B = 1.164 * (Y - 16) + 2.018 * (U - 128) + // G = 1.164 * (Y - 16) - 0.813 * (V - 128) - 0.391 * (U - 128) + // R = 1.164 * (Y - 16) + 1.596 * (V - 128) + + // B = 298/256 * (Y - 16) + 517/256 * (U - 128) + // G = .................. - 208/256 * (V - 128) - 100/256 * (U - 128) + // R = .................. + 409/256 * (V - 128) + + // min_B = (298 * (- 16) + 517 * (- 128)) / 256 = -277 + // min_G = (298 * (- 16) - 208 * (255 - 128) - 100 * (255 - 128)) / 256 = -172 + // min_R = (298 * (- 16) + 409 * (- 128)) / 256 = -223 + + // max_B = (298 * (255 - 16) + 517 * (255 - 128)) / 256 = 534 + // max_G = (298 * (255 - 16) - 208 * (- 128) - 100 * (- 128)) / 256 = 432 + // max_R = (298 * (255 - 16) + 409 * (255 - 128)) / 256 = 481 + + // clip range -278 .. 535 + + signed y1 = (signed)src_y[x] - 16; + signed y2 = (signed)src_y[x + 1] - 16; + +#if QCOM_YUV + signed u = (signed)src_u[x & ~1] - 128; + signed v = (signed)src_u[(x & ~1) + 1] - 128; +#else + signed u = (signed)src_u[x / 2] - 128; + signed v = (signed)src_v[x / 2] - 128; +#endif + + signed u_b = u * 517; + signed u_g = -u * 100; + signed v_g = -v * 208; + signed v_r = v * 409; + + signed tmp1 = y1 * 298; + signed b1 = (tmp1 + u_b) / 256; + signed g1 = (tmp1 + v_g + u_g) / 256; + signed r1 = (tmp1 + v_r) / 256; + + signed tmp2 = y2 * 298; + signed b2 = (tmp2 + u_b) / 256; + signed g2 = (tmp2 + v_g + u_g) / 256; + signed r2 = (tmp2 + v_r) / 256; + + uint32_t rgb1 = + ((kAdjustedClip[r1] >> 3) << 11) + | ((kAdjustedClip[g1] >> 2) << 5) + | (kAdjustedClip[b1] >> 3); + + uint32_t rgb2 = + ((kAdjustedClip[r2] >> 3) << 11) + | ((kAdjustedClip[g2] >> 2) << 5) + | (kAdjustedClip[b2] >> 3); + + dst_ptr[x / 2] = (rgb2 << 16) | rgb1; + } + + src_y += mDecodedWidth; + + if (y & 1) { +#if QCOM_YUV + src_u += mDecodedWidth; +#else + src_u += mDecodedWidth / 2; + src_v += mDecodedWidth / 2; +#endif + } + + dst_ptr += mDecodedWidth / 2; + } + + mISurface->postBuffer(offset); + mIndex = 1 - mIndex; +} + +} // namespace android diff --git a/media/libstagefright/SurfaceRenderer.cpp b/media/libstagefright/SurfaceRenderer.cpp new file mode 100644 index 0000000..e54288d --- /dev/null +++ b/media/libstagefright/SurfaceRenderer.cpp @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2009 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_TAG "SurfaceRenderer" +#include <utils/Log.h> + +#undef NDEBUG +#include <assert.h> + +#include <media/stagefright/SurfaceRenderer.h> +#include <ui/Surface.h> + +namespace android { + +SurfaceRenderer::SurfaceRenderer( + const sp<Surface> &surface, + size_t displayWidth, size_t displayHeight, + size_t decodedWidth, size_t decodedHeight) + : mSurface(surface), + mDisplayWidth(displayWidth), + mDisplayHeight(displayHeight), + mDecodedWidth(decodedWidth), + mDecodedHeight(decodedHeight) { +} + +SurfaceRenderer::~SurfaceRenderer() { +} + +void SurfaceRenderer::render( + const void *data, size_t size, void *platformPrivate) { + Surface::SurfaceInfo info; + status_t err = mSurface->lock(&info); + if (err != OK) { + return; + } + + const uint8_t *src = (const uint8_t *)data; + uint8_t *dst = (uint8_t *)info.bits; + + for (size_t i = 0; i < mDisplayHeight; ++i) { + memcpy(dst, src, mDisplayWidth); + src += mDecodedWidth; + dst += mDisplayWidth; + } + src += (mDecodedHeight - mDisplayHeight) * mDecodedWidth; + + for (size_t i = 0; i < (mDisplayHeight + 1) / 2; ++i) { + memcpy(dst, src, (mDisplayWidth + 1) & ~1); + src += (mDecodedWidth + 1) & ~1; + dst += (mDisplayWidth + 1) & ~1; + } + + mSurface->unlockAndPost(); +} + +} // namespace android diff --git a/media/libstagefright/TimeSource.cpp b/media/libstagefright/TimeSource.cpp new file mode 100644 index 0000000..7deb310 --- /dev/null +++ b/media/libstagefright/TimeSource.cpp @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2009 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. + */ + +#include <sys/time.h> + +#include <media/stagefright/TimeSource.h> + +namespace android { + +SystemTimeSource::SystemTimeSource() + : mStartTimeUs(GetSystemTimeUs()) { +} + +int64_t SystemTimeSource::getRealTimeUs() { + return GetSystemTimeUs() - mStartTimeUs; +} + +// static +int64_t SystemTimeSource::GetSystemTimeUs() { + struct timeval tv; + gettimeofday(&tv, NULL); + + return (int64_t)tv.tv_sec * 1000000 + tv.tv_usec; +} + +} // namespace android + diff --git a/media/libstagefright/TimedEventQueue.cpp b/media/libstagefright/TimedEventQueue.cpp new file mode 100644 index 0000000..10cf81a --- /dev/null +++ b/media/libstagefright/TimedEventQueue.cpp @@ -0,0 +1,206 @@ +/* + * Copyright (C) 2009 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. + */ + +#undef __STRICT_ANSI__ +#define __STDINT_LIMITS +#include <stdint.h> + +#define LOG_TAG "TimedEventQueue" +#include <utils/Log.h> + +#include <sys/time.h> + +#undef NDEBUG +#include <assert.h> + +#include <media/stagefright/TimedEventQueue.h> + +namespace android { + +TimedEventQueue::TimedEventQueue() + : mRunning(false), + mStopped(false) { +} + +TimedEventQueue::~TimedEventQueue() { + stop(); +} + +void TimedEventQueue::start() { + if (mRunning) { + return; + } + + mStopped = false; + + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); + + pthread_create(&mThread, &attr, ThreadWrapper, this); + + pthread_attr_destroy(&attr); + + mRunning = true; +} + +void TimedEventQueue::stop(bool flush) { + if (!mRunning) { + return; + } + + if (flush) { + postEventToBack(new StopEvent); + } else { + postTimedEvent(new StopEvent, INT64_MIN); + } + + void *dummy; + pthread_join(mThread, &dummy); + + mQueue.clear(); + + mRunning = false; +} + +void TimedEventQueue::postEvent(const sp<Event> &event) { + // Reserve an earlier timeslot an INT64_MIN to be able to post + // the StopEvent to the absolute head of the queue. + postTimedEvent(event, INT64_MIN + 1); +} + +void TimedEventQueue::postEventToBack(const sp<Event> &event) { + postTimedEvent(event, INT64_MAX); +} + +void TimedEventQueue::postEventWithDelay( + const sp<Event> &event, int64_t delay_us) { + assert(delay_us >= 0); + postTimedEvent(event, getRealTimeUs() + delay_us); +} + +void TimedEventQueue::postTimedEvent( + const sp<Event> &event, int64_t realtime_us) { + Mutex::Autolock autoLock(mLock); + + List<QueueItem>::iterator it = mQueue.begin(); + while (it != mQueue.end() && realtime_us >= (*it).realtime_us) { + ++it; + } + + QueueItem item; + item.event = event; + item.realtime_us = realtime_us; + + if (it == mQueue.begin()) { + mQueueHeadChangedCondition.signal(); + } + + mQueue.insert(it, item); + + mQueueNotEmptyCondition.signal(); +} + +bool TimedEventQueue::cancelEvent(const sp<Event> &event) { + Mutex::Autolock autoLock(mLock); + + List<QueueItem>::iterator it = mQueue.begin(); + while (it != mQueue.end() && (*it).event != event) { + ++it; + } + + if (it == mQueue.end()) { + return false; + } + + if (it == mQueue.begin()) { + mQueueHeadChangedCondition.signal(); + } + + mQueue.erase(it); + + return true; +} + +// static +int64_t TimedEventQueue::getRealTimeUs() { + struct timeval tv; + gettimeofday(&tv, NULL); + + return (int64_t)tv.tv_sec * 1000000 + tv.tv_usec; +} + +// static +void *TimedEventQueue::ThreadWrapper(void *me) { + static_cast<TimedEventQueue *>(me)->threadEntry(); + + return NULL; +} + +void TimedEventQueue::threadEntry() { + for (;;) { + int64_t now_us; + sp<Event> event; + + { + Mutex::Autolock autoLock(mLock); + + if (mStopped) { + break; + } + + while (mQueue.empty()) { + mQueueNotEmptyCondition.wait(mLock); + } + + List<QueueItem>::iterator it; + for (;;) { + it = mQueue.begin(); + + now_us = getRealTimeUs(); + int64_t when_us = (*it).realtime_us; + + int64_t delay_us; + if (when_us < 0 || when_us == INT64_MAX) { + delay_us = 0; + } else { + delay_us = when_us - now_us; + } + + if (delay_us <= 0) { + break; + } + + status_t err = mQueueHeadChangedCondition.waitRelative( + mLock, delay_us * 1000); + + if (err == -ETIMEDOUT) { + now_us = getRealTimeUs(); + break; + } + } + + event = (*it).event; + mQueue.erase(it); + } + + // Fire event with the lock NOT held. + event->fire(this, now_us); + } +} + +} // namespace android + diff --git a/media/libstagefright/Utils.cpp b/media/libstagefright/Utils.cpp new file mode 100644 index 0000000..2720f93 --- /dev/null +++ b/media/libstagefright/Utils.cpp @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2009 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. + */ + +#include <arpa/inet.h> + +#include <media/stagefright/Utils.h> + +namespace android { + +uint16_t U16_AT(const uint8_t *ptr) { + return ptr[0] << 8 | ptr[1]; +} + +uint32_t U32_AT(const uint8_t *ptr) { + return ptr[0] << 24 | ptr[1] << 16 | ptr[2] << 8 | ptr[3]; +} + +uint64_t U64_AT(const uint8_t *ptr) { + return ((uint64_t)U32_AT(ptr)) << 32 | U32_AT(ptr + 4); +} + +// XXX warning: these won't work on big-endian host. +uint64_t ntoh64(uint64_t x) { + return ((uint64_t)ntohl(x & 0xffffffff) << 32) | ntohl(x >> 32); +} + +uint64_t hton64(uint64_t x) { + return ((uint64_t)htonl(x & 0xffffffff) << 32) | htonl(x >> 32); +} + +} // namespace android + diff --git a/media/libstagefright/omx/Android.mk b/media/libstagefright/omx/Android.mk new file mode 100644 index 0000000..9c6d475 --- /dev/null +++ b/media/libstagefright/omx/Android.mk @@ -0,0 +1,27 @@ +ifeq ($(BUILD_WITH_STAGEFRIGHT),true) + +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +# Set up the OpenCore variables. +include external/opencore/Config.mk +LOCAL_C_INCLUDES := $(PV_INCLUDES) +LOCAL_CFLAGS := $(PV_CFLAGS_MINUS_VISIBILITY) + +LOCAL_SRC_FILES:= \ + OMX.cpp + +LOCAL_SHARED_LIBRARIES := \ + libbinder \ + libmedia \ + libutils \ + libui \ + libopencore_common + +LOCAL_PRELINK_MODULE:= false + +LOCAL_MODULE:= libstagefright_omx + +include $(BUILD_SHARED_LIBRARY) + +endif diff --git a/media/libstagefright/omx/OMX.cpp b/media/libstagefright/omx/OMX.cpp new file mode 100644 index 0000000..c18f5ce --- /dev/null +++ b/media/libstagefright/omx/OMX.cpp @@ -0,0 +1,621 @@ +/* + * Copyright (C) 2009 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 "OMX" +#include <utils/Log.h> + +#include <sys/socket.h> + +#undef NDEBUG +#include <assert.h> + +#include "OMX.h" +#include "pv_omxcore.h" + +#include <binder/IMemory.h> + +#include <OMX_Component.h> + +namespace android { + +class NodeMeta { +public: + NodeMeta(OMX *owner) + : mOwner(owner), + mHandle(NULL) { + } + + OMX *owner() const { + return mOwner; + } + + void setHandle(OMX_HANDLETYPE handle) { + assert(mHandle == NULL); + mHandle = handle; + } + + OMX_HANDLETYPE handle() const { + return mHandle; + } + + void setObserver(const sp<IOMXObserver> &observer) { + mObserver = observer; + } + + sp<IOMXObserver> observer() { + return mObserver; + } + +private: + OMX *mOwner; + OMX_HANDLETYPE mHandle; + sp<IOMXObserver> mObserver; + + NodeMeta(const NodeMeta &); + NodeMeta &operator=(const NodeMeta &); +}; + +class BufferMeta { +public: + BufferMeta(OMX *owner, const sp<IMemory> &mem, bool is_backup = false) + : mOwner(owner), + mMem(mem), + mIsBackup(is_backup) { + } + + BufferMeta(OMX *owner, size_t size) + : mOwner(owner), + mSize(size), + mIsBackup(false) { + } + + void CopyFromOMX(const OMX_BUFFERHEADERTYPE *header) { + if (!mIsBackup) { + return; + } + + memcpy((OMX_U8 *)mMem->pointer() + header->nOffset, + header->pBuffer + header->nOffset, + header->nFilledLen); + } + + void CopyToOMX(const OMX_BUFFERHEADERTYPE *header) { + if (!mIsBackup) { + return; + } + + memcpy(header->pBuffer + header->nOffset, + (const OMX_U8 *)mMem->pointer() + header->nOffset, + header->nFilledLen); + } + +private: + OMX *mOwner; + sp<IMemory> mMem; + size_t mSize; + bool mIsBackup; + + BufferMeta(const BufferMeta &); + BufferMeta &operator=(const BufferMeta &); +}; + +// static +OMX_CALLBACKTYPE OMX::kCallbacks = { + &OnEvent, &OnEmptyBufferDone, &OnFillBufferDone +}; + +// static +OMX_ERRORTYPE OMX::OnEvent( + OMX_IN OMX_HANDLETYPE hComponent, + OMX_IN OMX_PTR pAppData, + OMX_IN OMX_EVENTTYPE eEvent, + OMX_IN OMX_U32 nData1, + OMX_IN OMX_U32 nData2, + OMX_IN OMX_PTR pEventData) { + NodeMeta *meta = static_cast<NodeMeta *>(pAppData); + return meta->owner()->OnEvent(meta, eEvent, nData1, nData2, pEventData); +} + +// static +OMX_ERRORTYPE OMX::OnEmptyBufferDone( + OMX_IN OMX_HANDLETYPE hComponent, + OMX_IN OMX_PTR pAppData, + OMX_IN OMX_BUFFERHEADERTYPE* pBuffer) { + NodeMeta *meta = static_cast<NodeMeta *>(pAppData); + return meta->owner()->OnEmptyBufferDone(meta, pBuffer); +} + +// static +OMX_ERRORTYPE OMX::OnFillBufferDone( + OMX_IN OMX_HANDLETYPE hComponent, + OMX_IN OMX_PTR pAppData, + OMX_IN OMX_BUFFERHEADERTYPE* pBuffer) { + NodeMeta *meta = static_cast<NodeMeta *>(pAppData); + return meta->owner()->OnFillBufferDone(meta, pBuffer); +} + +OMX::OMX() +#if IOMX_USES_SOCKETS + : mSock(-1) +#endif +{ +} + +OMX::~OMX() { +#if IOMX_USES_SOCKETS + assert(mSock < 0); +#endif +} + +#if IOMX_USES_SOCKETS +status_t OMX::connect(int *sd) { + Mutex::Autolock autoLock(mLock); + + if (mSock >= 0) { + return UNKNOWN_ERROR; + } + + int sockets[2]; + if (socketpair(AF_UNIX, SOCK_DGRAM, 0, sockets) < 0) { + return UNKNOWN_ERROR; + } + + mSock = sockets[0]; + *sd = sockets[1]; + + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); + + int err = pthread_create(&mThread, &attr, ThreadWrapper, this); + assert(err == 0); + + pthread_attr_destroy(&attr); + + return OK; +} + +// static +void *OMX::ThreadWrapper(void *me) { + ((OMX *)me)->threadEntry(); + + return NULL; +} + +void OMX::threadEntry() { + bool done = false; + while (!done) { + omx_message msg; + ssize_t n = recv(mSock, &msg, sizeof(msg), 0); + + if (n <= 0) { + break; + } + + Mutex::Autolock autoLock(mLock); + + switch (msg.type) { + case omx_message::FILL_BUFFER: + { + OMX_BUFFERHEADERTYPE *header = + static_cast<OMX_BUFFERHEADERTYPE *>( + msg.u.buffer_data.buffer); + + header->nFilledLen = 0; + header->nOffset = 0; + header->nFlags = 0; + + NodeMeta *node_meta = static_cast<NodeMeta *>( + msg.u.buffer_data.node); + + LOGV("FillThisBuffer buffer=%p", header); + + OMX_ERRORTYPE err = + OMX_FillThisBuffer(node_meta->handle(), header); + assert(err == OMX_ErrorNone); + break; + } + + case omx_message::EMPTY_BUFFER: + { + OMX_BUFFERHEADERTYPE *header = + static_cast<OMX_BUFFERHEADERTYPE *>( + msg.u.extended_buffer_data.buffer); + + header->nFilledLen = msg.u.extended_buffer_data.range_length; + header->nOffset = msg.u.extended_buffer_data.range_offset; + header->nFlags = msg.u.extended_buffer_data.flags; + header->nTimeStamp = msg.u.extended_buffer_data.timestamp; + + BufferMeta *buffer_meta = + static_cast<BufferMeta *>(header->pAppPrivate); + buffer_meta->CopyToOMX(header); + + NodeMeta *node_meta = static_cast<NodeMeta *>( + msg.u.extended_buffer_data.node); + + LOGV("EmptyThisBuffer buffer=%p", header); + + OMX_ERRORTYPE err = + OMX_EmptyThisBuffer(node_meta->handle(), header); + assert(err == OMX_ErrorNone); + break; + } + + case omx_message::SEND_COMMAND: + { + NodeMeta *node_meta = static_cast<NodeMeta *>( + msg.u.send_command_data.node); + + OMX_ERRORTYPE err = + OMX_SendCommand( + node_meta->handle(), msg.u.send_command_data.cmd, + msg.u.send_command_data.param, NULL); + assert(err == OMX_ErrorNone); + break; + } + + case omx_message::DISCONNECT: + { + omx_message msg; + msg.type = omx_message::DISCONNECTED; + ssize_t n = send(mSock, &msg, sizeof(msg), 0); + assert(n > 0 && static_cast<size_t>(n) == sizeof(msg)); + done = true; + break; + } + + default: + LOGE("received unknown omx_message type %d", msg.type); + break; + } + } + + Mutex::Autolock autoLock(mLock); + close(mSock); + mSock = -1; +} +#endif + +status_t OMX::list_nodes(List<String8> *list) { + OMX_MasterInit(); // XXX Put this somewhere else. + + list->clear(); + + OMX_U32 index = 0; + char componentName[256]; + while (OMX_MasterComponentNameEnum(componentName, sizeof(componentName), index) + == OMX_ErrorNone) { + list->push_back(String8(componentName)); + + ++index; + } + + return OK; +} + +status_t OMX::allocate_node(const char *name, node_id *node) { + Mutex::Autolock autoLock(mLock); + + *node = 0; + + OMX_MasterInit(); // XXX Put this somewhere else. + + NodeMeta *meta = new NodeMeta(this); + + OMX_HANDLETYPE handle; + OMX_ERRORTYPE err = OMX_MasterGetHandle( + &handle, const_cast<char *>(name), meta, &kCallbacks); + + if (err != OMX_ErrorNone) { + LOGE("FAILED to allocate omx component '%s'", name); + + delete meta; + meta = NULL; + + return UNKNOWN_ERROR; + } + + meta->setHandle(handle); + + *node = meta; + + return OK; +} + +status_t OMX::free_node(node_id node) { + Mutex::Autolock autoLock(mLock); + + NodeMeta *meta = static_cast<NodeMeta *>(node); + + OMX_ERRORTYPE err = OMX_MasterFreeHandle(meta->handle()); + + delete meta; + meta = NULL; + + return (err != OMX_ErrorNone) ? UNKNOWN_ERROR : OK; +} + +status_t OMX::send_command( + node_id node, OMX_COMMANDTYPE cmd, OMX_S32 param) { + Mutex::Autolock autoLock(mLock); + +#if IOMX_USES_SOCKETS + if (mSock < 0) { + return UNKNOWN_ERROR; + } +#endif + + NodeMeta *meta = static_cast<NodeMeta *>(node); + OMX_ERRORTYPE err = OMX_SendCommand(meta->handle(), cmd, param, NULL); + + return (err != OMX_ErrorNone) ? UNKNOWN_ERROR : OK; +} + +status_t OMX::get_parameter( + node_id node, OMX_INDEXTYPE index, + void *params, size_t size) { + Mutex::Autolock autoLock(mLock); + + NodeMeta *meta = static_cast<NodeMeta *>(node); + OMX_ERRORTYPE err = OMX_GetParameter(meta->handle(), index, params); + + return (err != OMX_ErrorNone) ? UNKNOWN_ERROR : OK; +} + +status_t OMX::set_parameter( + node_id node, OMX_INDEXTYPE index, + const void *params, size_t size) { + Mutex::Autolock autoLock(mLock); + + NodeMeta *meta = static_cast<NodeMeta *>(node); + OMX_ERRORTYPE err = + OMX_SetParameter(meta->handle(), index, const_cast<void *>(params)); + + return (err != OMX_ErrorNone) ? UNKNOWN_ERROR : OK; +} + +status_t OMX::use_buffer( + node_id node, OMX_U32 port_index, const sp<IMemory> ¶ms, + buffer_id *buffer) { + Mutex::Autolock autoLock(mLock); + + BufferMeta *buffer_meta = new BufferMeta(this, params); + + OMX_BUFFERHEADERTYPE *header; + + NodeMeta *node_meta = static_cast<NodeMeta *>(node); + OMX_ERRORTYPE err = + OMX_UseBuffer(node_meta->handle(), &header, port_index, buffer_meta, + params->size(), static_cast<OMX_U8 *>(params->pointer())); + + if (err != OMX_ErrorNone) { + LOGE("OMX_UseBuffer failed with error %d (0x%08x)", err, err); + + delete buffer_meta; + buffer_meta = NULL; + + *buffer = 0; + return UNKNOWN_ERROR; + } + + *buffer = header; + + return OK; +} + +status_t OMX::allocate_buffer( + node_id node, OMX_U32 port_index, size_t size, + buffer_id *buffer) { + Mutex::Autolock autoLock(mLock); + + BufferMeta *buffer_meta = new BufferMeta(this, size); + + OMX_BUFFERHEADERTYPE *header; + + NodeMeta *node_meta = static_cast<NodeMeta *>(node); + OMX_ERRORTYPE err = + OMX_AllocateBuffer(node_meta->handle(), &header, port_index, + buffer_meta, size); + + if (err != OMX_ErrorNone) { + delete buffer_meta; + buffer_meta = NULL; + + *buffer = 0; + return UNKNOWN_ERROR; + } + + *buffer = header; + + return OK; +} + +status_t OMX::allocate_buffer_with_backup( + node_id node, OMX_U32 port_index, const sp<IMemory> ¶ms, + buffer_id *buffer) { + Mutex::Autolock autoLock(mLock); + + BufferMeta *buffer_meta = new BufferMeta(this, params, true); + + OMX_BUFFERHEADERTYPE *header; + + NodeMeta *node_meta = static_cast<NodeMeta *>(node); + OMX_ERRORTYPE err = + OMX_AllocateBuffer( + node_meta->handle(), &header, port_index, buffer_meta, + params->size()); + + if (err != OMX_ErrorNone) { + delete buffer_meta; + buffer_meta = NULL; + + *buffer = 0; + return UNKNOWN_ERROR; + } + + *buffer = header; + + return OK; +} + +status_t OMX::free_buffer(node_id node, OMX_U32 port_index, buffer_id buffer) { + OMX_BUFFERHEADERTYPE *header = (OMX_BUFFERHEADERTYPE *)buffer; + BufferMeta *buffer_meta = static_cast<BufferMeta *>(header->pAppPrivate); + + NodeMeta *node_meta = static_cast<NodeMeta *>(node); + OMX_ERRORTYPE err = + OMX_FreeBuffer(node_meta->handle(), port_index, header); + + delete buffer_meta; + buffer_meta = NULL; + + return (err != OMX_ErrorNone) ? UNKNOWN_ERROR : OK; +} + +OMX_ERRORTYPE OMX::OnEvent( + NodeMeta *meta, + OMX_IN OMX_EVENTTYPE eEvent, + OMX_IN OMX_U32 nData1, + OMX_IN OMX_U32 nData2, + OMX_IN OMX_PTR pEventData) { + LOGV("OnEvent(%d, %ld, %ld)", eEvent, nData1, nData2); + + omx_message msg; + msg.type = omx_message::EVENT; + msg.u.event_data.node = meta; + msg.u.event_data.event = eEvent; + msg.u.event_data.data1 = nData1; + msg.u.event_data.data2 = nData2; + +#if !IOMX_USES_SOCKETS + sp<IOMXObserver> observer = meta->observer(); + if (observer.get() != NULL) { + observer->on_message(msg); + } +#else + assert(mSock >= 0); + + ssize_t n = send(mSock, &msg, sizeof(msg), 0); + assert(n > 0 && static_cast<size_t>(n) == sizeof(msg)); +#endif + + return OMX_ErrorNone; +} + +OMX_ERRORTYPE OMX::OnEmptyBufferDone( + NodeMeta *meta, OMX_IN OMX_BUFFERHEADERTYPE *pBuffer) { + LOGV("OnEmptyBufferDone buffer=%p", pBuffer); + + omx_message msg; + msg.type = omx_message::EMPTY_BUFFER_DONE; + msg.u.buffer_data.node = meta; + msg.u.buffer_data.buffer = pBuffer; + +#if !IOMX_USES_SOCKETS + sp<IOMXObserver> observer = meta->observer(); + if (observer.get() != NULL) { + observer->on_message(msg); + } +#else + assert(mSock >= 0); + ssize_t n = send(mSock, &msg, sizeof(msg), 0); + assert(n > 0 && static_cast<size_t>(n) == sizeof(msg)); +#endif + + return OMX_ErrorNone; +} + +OMX_ERRORTYPE OMX::OnFillBufferDone( + NodeMeta *meta, OMX_IN OMX_BUFFERHEADERTYPE *pBuffer) { + LOGV("OnFillBufferDone buffer=%p", pBuffer); + BufferMeta *buffer_meta = static_cast<BufferMeta *>(pBuffer->pAppPrivate); + buffer_meta->CopyFromOMX(pBuffer); + + omx_message msg; + msg.type = omx_message::FILL_BUFFER_DONE; + msg.u.extended_buffer_data.node = meta; + msg.u.extended_buffer_data.buffer = pBuffer; + msg.u.extended_buffer_data.range_offset = pBuffer->nOffset; + msg.u.extended_buffer_data.range_length = pBuffer->nFilledLen; + msg.u.extended_buffer_data.flags = pBuffer->nFlags; + msg.u.extended_buffer_data.timestamp = pBuffer->nTimeStamp; + msg.u.extended_buffer_data.platform_private = pBuffer->pPlatformPrivate; + +#if !IOMX_USES_SOCKETS + sp<IOMXObserver> observer = meta->observer(); + if (observer.get() != NULL) { + observer->on_message(msg); + } +#else + assert(mSock >= 0); + + ssize_t n = send(mSock, &msg, sizeof(msg), 0); + assert(n > 0 && static_cast<size_t>(n) == sizeof(msg)); +#endif + + return OMX_ErrorNone; +} + +#if !IOMX_USES_SOCKETS +status_t OMX::observe_node( + node_id node, const sp<IOMXObserver> &observer) { + NodeMeta *node_meta = static_cast<NodeMeta *>(node); + + node_meta->setObserver(observer); + + return OK; +} + +void OMX::fill_buffer(node_id node, buffer_id buffer) { + OMX_BUFFERHEADERTYPE *header = (OMX_BUFFERHEADERTYPE *)buffer; + header->nFilledLen = 0; + header->nOffset = 0; + header->nFlags = 0; + + NodeMeta *node_meta = static_cast<NodeMeta *>(node); + + OMX_ERRORTYPE err = + OMX_FillThisBuffer(node_meta->handle(), header); + assert(err == OMX_ErrorNone); +} + +void OMX::empty_buffer( + node_id node, + buffer_id buffer, + OMX_U32 range_offset, OMX_U32 range_length, + OMX_U32 flags, OMX_TICKS timestamp) { + OMX_BUFFERHEADERTYPE *header = (OMX_BUFFERHEADERTYPE *)buffer; + header->nFilledLen = range_length; + header->nOffset = range_offset; + header->nFlags = flags; + header->nTimeStamp = timestamp; + + BufferMeta *buffer_meta = + static_cast<BufferMeta *>(header->pAppPrivate); + buffer_meta->CopyToOMX(header); + + NodeMeta *node_meta = static_cast<NodeMeta *>(node); + + OMX_ERRORTYPE err = + OMX_EmptyThisBuffer(node_meta->handle(), header); + assert(err == OMX_ErrorNone); +} +#endif + +} // namespace android + diff --git a/media/libstagefright/omx/OMX.h b/media/libstagefright/omx/OMX.h new file mode 100644 index 0000000..ed4e5dd --- /dev/null +++ b/media/libstagefright/omx/OMX.h @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2009 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. + */ + +#ifndef ANDROID_OMX_H_ +#define ANDROID_OMX_H_ + +#include <pthread.h> + +#include <media/IOMX.h> +#include <utils/threads.h> + +namespace android { + +class NodeMeta; + +class OMX : public BnOMX { +public: + OMX(); + virtual ~OMX(); + +#if IOMX_USES_SOCKETS + virtual status_t connect(int *sd); +#endif + + virtual status_t list_nodes(List<String8> *list); + + virtual status_t allocate_node(const char *name, node_id *node); + virtual status_t free_node(node_id node); + + virtual status_t send_command( + node_id node, OMX_COMMANDTYPE cmd, OMX_S32 param); + + virtual status_t get_parameter( + node_id node, OMX_INDEXTYPE index, + void *params, size_t size); + + virtual status_t set_parameter( + node_id node, OMX_INDEXTYPE index, + const void *params, size_t size); + + virtual status_t use_buffer( + node_id node, OMX_U32 port_index, const sp<IMemory> ¶ms, + buffer_id *buffer); + + virtual status_t allocate_buffer( + node_id node, OMX_U32 port_index, size_t size, + buffer_id *buffer); + + virtual status_t allocate_buffer_with_backup( + node_id node, OMX_U32 port_index, const sp<IMemory> ¶ms, + buffer_id *buffer); + + virtual status_t free_buffer( + node_id node, OMX_U32 port_index, buffer_id buffer); + +#if !IOMX_USES_SOCKETS + virtual status_t observe_node( + node_id node, const sp<IOMXObserver> &observer); + + virtual void fill_buffer(node_id node, buffer_id buffer); + + virtual void empty_buffer( + node_id node, + buffer_id buffer, + OMX_U32 range_offset, OMX_U32 range_length, + OMX_U32 flags, OMX_TICKS timestamp); +#endif + +private: + static OMX_CALLBACKTYPE kCallbacks; + +#if IOMX_USES_SOCKETS + int mSock; + pthread_t mThread; + + static void *ThreadWrapper(void *me); + void threadEntry(); +#endif + + Mutex mLock; + + static OMX_ERRORTYPE OnEvent( + OMX_IN OMX_HANDLETYPE hComponent, + OMX_IN OMX_PTR pAppData, + OMX_IN OMX_EVENTTYPE eEvent, + OMX_IN OMX_U32 nData1, + OMX_IN OMX_U32 nData2, + OMX_IN OMX_PTR pEventData); + + static OMX_ERRORTYPE OnEmptyBufferDone( + OMX_IN OMX_HANDLETYPE hComponent, + OMX_IN OMX_PTR pAppData, + OMX_IN OMX_BUFFERHEADERTYPE* pBuffer); + + static OMX_ERRORTYPE OnFillBufferDone( + OMX_IN OMX_HANDLETYPE hComponent, + OMX_IN OMX_PTR pAppData, + OMX_IN OMX_BUFFERHEADERTYPE* pBuffer); + + OMX_ERRORTYPE OnEvent( + NodeMeta *meta, + OMX_IN OMX_EVENTTYPE eEvent, + OMX_IN OMX_U32 nData1, + OMX_IN OMX_U32 nData2, + OMX_IN OMX_PTR pEventData); + + OMX_ERRORTYPE OnEmptyBufferDone( + NodeMeta *meta, OMX_IN OMX_BUFFERHEADERTYPE *pBuffer); + + OMX_ERRORTYPE OnFillBufferDone( + NodeMeta *meta, OMX_IN OMX_BUFFERHEADERTYPE *pBuffer); + + OMX(const OMX &); + OMX &operator=(const OMX &); +}; + +} // namespace android + +#endif // ANDROID_OMX_H_ diff --git a/media/libstagefright/string.cpp b/media/libstagefright/string.cpp new file mode 100644 index 0000000..5b16784 --- /dev/null +++ b/media/libstagefright/string.cpp @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2009 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. + */ + +#include <media/stagefright/string.h> + +namespace android { + +// static +string::size_type string::npos = (string::size_type)-1; + +string::string() { +} + +string::string(const char *s, size_t length) + : mString(s, length) { +} + +string::string(const string &from, size_type start, size_type length) + : mString(from.c_str() + start, length) { +} + +string::string(const char *s) + : mString(s) { +} + +const char *string::c_str() const { + return mString.string(); +} + +string::size_type string::size() const { + return mString.length(); +} + +void string::clear() { + mString = String8(); +} + +string::size_type string::find(char c) const { + char s[2]; + s[0] = c; + s[1] = '\0'; + + ssize_t index = mString.find(s); + + return index < 0 ? npos : (size_type)index; +} + +bool string::operator<(const string &other) const { + return mString < other.mString; +} + +bool string::operator==(const string &other) const { + return mString == other.mString; +} + +string &string::operator+=(char c) { + mString.append(&c, 1); + + return *this; +} + +void string::erase(size_t from, size_t length) { + String8 s(mString.string(), from); + s.append(mString.string() + from + length); + + mString = s; +} + +} // namespace android + |