diff options
Diffstat (limited to 'media')
55 files changed, 3132 insertions, 732 deletions
diff --git a/media/java/android/media/Ringtone.java b/media/java/android/media/Ringtone.java index e80d8aa..1713324 100644 --- a/media/java/android/media/Ringtone.java +++ b/media/java/android/media/Ringtone.java @@ -137,11 +137,17 @@ public class Ringtone { cursor = res.query(uri, MEDIA_COLUMNS, null, null, null); } - if (cursor != null && cursor.getCount() == 1) { - cursor.moveToFirst(); - return cursor.getString(2); - } else { - title = uri.getLastPathSegment(); + try { + if (cursor != null && cursor.getCount() == 1) { + cursor.moveToFirst(); + return cursor.getString(2); + } else { + title = uri.getLastPathSegment(); + } + } finally { + if (cursor != null) { + cursor.close(); + } } } } diff --git a/media/java/android/media/ThumbnailUtil.java b/media/java/android/media/ThumbnailUtil.java index f9d69fb..0cf4e76 100644 --- a/media/java/android/media/ThumbnailUtil.java +++ b/media/java/android/media/ThumbnailUtil.java @@ -33,10 +33,9 @@ import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.Rect; import android.media.MediaMetadataRetriever; +import android.media.MediaFile.MediaFileType; -import java.io.ByteArrayOutputStream; import java.io.FileDescriptor; -import java.io.FileNotFoundException; import java.io.IOException; import java.io.OutputStream; @@ -295,7 +294,7 @@ public class ThumbnailUtil { * @param uri URI of original image * @param origId image id * @param kind either MINI_KIND or MICRO_KIND - * @param saveImage Whether to save MINI_KIND thumbnail obtained in this method. + * @param saveMini Whether to save MINI_KIND thumbnail obtained in this method. * @return Bitmap */ public static Bitmap createImageThumbnail(ContentResolver cr, String filePath, Uri uri, @@ -305,16 +304,12 @@ public class ThumbnailUtil { ThumbnailUtil.THUMBNAIL_TARGET_SIZE : ThumbnailUtil.MINI_THUMB_TARGET_SIZE; int maxPixels = wantMini ? ThumbnailUtil.THUMBNAIL_MAX_NUM_PIXELS : ThumbnailUtil.MINI_THUMB_MAX_NUM_PIXELS; - byte[] thumbData = createThumbnailFromEXIF(filePath, targetSize); + SizedThumbnailBitmap sizedThumbnailBitmap = new SizedThumbnailBitmap(); Bitmap bitmap = null; - - if (thumbData != null) { - BitmapFactory.Options options = new BitmapFactory.Options(); - options.inSampleSize = computeSampleSize(options, targetSize, maxPixels); - options.inDither = false; - options.inPreferredConfig = Bitmap.Config.ARGB_8888; - options.inJustDecodeBounds = false; - bitmap = BitmapFactory.decodeByteArray(thumbData, 0, thumbData.length, options); + MediaFileType fileType = MediaFile.getFileType(filePath); + if (fileType != null && fileType.fileType == MediaFile.FILE_TYPE_JPEG) { + createThumbnailFromEXIF(filePath, targetSize, maxPixels, sizedThumbnailBitmap); + bitmap = sizedThumbnailBitmap.mBitmap; } if (bitmap == null) { @@ -326,9 +321,11 @@ public class ThumbnailUtil { } if (saveMini) { - if (thumbData != null) { - ThumbnailUtil.storeThumbnail(cr, origId, thumbData, bitmap.getWidth(), - bitmap.getHeight()); + if (sizedThumbnailBitmap.mThumbnailData != null) { + ThumbnailUtil.storeThumbnail(cr, origId, + sizedThumbnailBitmap.mThumbnailData, + sizedThumbnailBitmap.mThumbnailWidth, + sizedThumbnailBitmap.mThumbnailHeight); } else { ThumbnailUtil.storeThumbnail(cr, origId, bitmap); } @@ -454,6 +451,7 @@ public class ThumbnailUtil { Cursor c = cr.query(thumbUri, THUMB_PROJECTION, Thumbnails.IMAGE_ID + "=?", new String[]{String.valueOf(origId)}, null); + if (c == null) return null; try { if (c.moveToNext()) { return ContentUris.withAppendedId(thumbUri, c.getLong(0)); @@ -482,6 +480,7 @@ public class ThumbnailUtil { if (thumb == null) return false; try { Uri uri = getImageThumbnailUri(cr, origId, thumb.getWidth(), thumb.getHeight()); + if (uri == null) return false; OutputStream thumbOut = cr.openOutputStream(uri); thumb.compress(Bitmap.CompressFormat.JPEG, 85, thumbOut); thumbOut.close(); @@ -514,31 +513,69 @@ public class ThumbnailUtil { } } - // Extract thumbnail in image that meets the targetSize criteria. - static byte[] createThumbnailFromEXIF(String filePath, int targetSize) { - if (filePath == null) return null; - - try { - ExifInterface exif = new ExifInterface(filePath); - if (exif == null) return null; - byte [] thumbData = exif.getThumbnail(); - if (thumbData == null) return null; - // Sniff the size of the EXIF thumbnail before decoding it. Photos - // from the device will pass, but images that are side loaded from - // other cameras may not. - BitmapFactory.Options options = new BitmapFactory.Options(); - options.inJustDecodeBounds = true; - BitmapFactory.decodeByteArray(thumbData, 0, thumbData.length, options); + // SizedThumbnailBitmap contains the bitmap, which is downsampled either from + // the thumbnail in exif or the full image. + // mThumbnailData, mThumbnailWidth and mThumbnailHeight are set together only if mThumbnail is not null. + // The width/height of the sized bitmap may be different from mThumbnailWidth/mThumbnailHeight. + private static class SizedThumbnailBitmap { + public byte[] mThumbnailData; + public Bitmap mBitmap; + public int mThumbnailWidth; + public int mThumbnailHeight; + } - int width = options.outWidth; - int height = options.outHeight; + // Creates a bitmap by either downsampling from the thumbnail in EXIF or the full image. + // The functions returns a SizedThumbnailBitmap, + // which contains a downsampled bitmap and the thumbnail data in EXIF if exists. + private static void createThumbnailFromEXIF(String filePath, int targetSize, + int maxPixels, SizedThumbnailBitmap sizedThumbBitmap) { + if (filePath == null) return; - if (width >= targetSize && height >= targetSize) { - return thumbData; + ExifInterface exif = null; + byte [] thumbData = null; + try { + exif = new ExifInterface(filePath); + if (exif != null) { + thumbData = exif.getThumbnail(); } } catch (IOException ex) { Log.w(TAG, ex); } - return null; + + BitmapFactory.Options fullOptions = new BitmapFactory.Options(); + BitmapFactory.Options exifOptions = new BitmapFactory.Options(); + int exifThumbWidth = 0; + int fullThumbWidth = 0; + + // Compute exifThumbWidth. + if (thumbData != null) { + exifOptions.inJustDecodeBounds = true; + BitmapFactory.decodeByteArray(thumbData, 0, thumbData.length, exifOptions); + exifOptions.inSampleSize = computeSampleSize(exifOptions, targetSize, maxPixels); + exifThumbWidth = exifOptions.outWidth / exifOptions.inSampleSize; + } + + // Compute fullThumbWidth. + fullOptions.inJustDecodeBounds = true; + BitmapFactory.decodeFile(filePath, fullOptions); + fullOptions.inSampleSize = computeSampleSize(fullOptions, targetSize, maxPixels); + fullThumbWidth = fullOptions.outWidth / fullOptions.inSampleSize; + + // Choose the larger thumbnail as the returning sizedThumbBitmap. + if (exifThumbWidth >= fullThumbWidth) { + int width = exifOptions.outWidth; + int height = exifOptions.outHeight; + exifOptions.inJustDecodeBounds = false; + sizedThumbBitmap.mBitmap = BitmapFactory.decodeByteArray(thumbData, 0, + thumbData.length, exifOptions); + if (sizedThumbBitmap.mBitmap != null) { + sizedThumbBitmap.mThumbnailData = thumbData; + sizedThumbBitmap.mThumbnailWidth = width; + sizedThumbBitmap.mThumbnailHeight = height; + } + } else { + fullOptions.inJustDecodeBounds = false; + sizedThumbBitmap.mBitmap = BitmapFactory.decodeFile(filePath, fullOptions); + } } } diff --git a/media/libmedia/IOMX.cpp b/media/libmedia/IOMX.cpp index 88a7064..76a9e7d 100644 --- a/media/libmedia/IOMX.cpp +++ b/media/libmedia/IOMX.cpp @@ -284,7 +284,7 @@ public: data.writeInterfaceToken(IOMX::getInterfaceDescriptor()); data.writeIntPtr((intptr_t)node); data.writeIntPtr((intptr_t)buffer); - remote()->transact(FILL_BUFFER, data, &reply, IBinder::FLAG_ONEWAY); + remote()->transact(FILL_BUFFER, data, &reply); return reply.readInt32(); } @@ -302,7 +302,7 @@ public: data.writeInt32(range_length); data.writeInt32(flags); data.writeInt64(timestamp); - remote()->transact(EMPTY_BUFFER, data, &reply, IBinder::FLAG_ONEWAY); + remote()->transact(EMPTY_BUFFER, data, &reply); return reply.readInt32(); } diff --git a/media/libmediaplayerservice/Android.mk b/media/libmediaplayerservice/Android.mk index fb569da..4784b8e 100644 --- a/media/libmediaplayerservice/Android.mk +++ b/media/libmediaplayerservice/Android.mk @@ -18,8 +18,10 @@ LOCAL_SRC_FILES:= \ ifeq ($(BUILD_WITH_FULL_STAGEFRIGHT),true) -LOCAL_SRC_FILES += \ - StagefrightPlayer.cpp +LOCAL_SRC_FILES += \ + StagefrightMetadataRetriever.cpp \ + StagefrightPlayer.cpp \ + StagefrightRecorder.cpp LOCAL_CFLAGS += -DBUILD_WITH_FULL_STAGEFRIGHT=1 diff --git a/media/libmediaplayerservice/MediaPlayerService.cpp b/media/libmediaplayerservice/MediaPlayerService.cpp index 0a6c365..b81684b 100644 --- a/media/libmediaplayerservice/MediaPlayerService.cpp +++ b/media/libmediaplayerservice/MediaPlayerService.cpp @@ -366,11 +366,44 @@ extern "C" void get_malloc_leak_info(uint8_t** info, size_t* overallSize, size_t* infoSize, size_t* totalMemory, size_t* backtraceSize); extern "C" void free_malloc_leak_info(uint8_t* info); +// Use the String-class below instead of String8 to allocate all memory +// beforehand and not reenter the heap while we are examining it... +struct MyString8 { + static const size_t MAX_SIZE = 256 * 1024; + + MyString8() + : mPtr((char *)malloc(MAX_SIZE)) { + *mPtr = '\0'; + } + + ~MyString8() { + free(mPtr); + } + + void append(const char *s) { + strcat(mPtr, s); + } + + const char *string() const { + return mPtr; + } + + size_t size() const { + return strlen(mPtr); + } + +private: + char *mPtr; + + MyString8(const MyString8 &); + MyString8 &operator=(const MyString8 &); +}; + void memStatus(int fd, const Vector<String16>& args) { const size_t SIZE = 256; char buffer[SIZE]; - String8 result; + MyString8 result; typedef struct { size_t size; diff --git a/media/libmediaplayerservice/MediaRecorderClient.cpp b/media/libmediaplayerservice/MediaRecorderClient.cpp index 95ee3e4..2ea7cc3 100644 --- a/media/libmediaplayerservice/MediaRecorderClient.cpp +++ b/media/libmediaplayerservice/MediaRecorderClient.cpp @@ -24,6 +24,7 @@ #include <unistd.h> #include <string.h> #include <cutils/atomic.h> +#include <cutils/properties.h> // for property_get #include <android_runtime/ActivityManager.h> #include <binder/IPCThreadState.h> #include <binder/IServiceManager.h> @@ -37,6 +38,8 @@ #include "MediaRecorderClient.h" #include "MediaPlayerService.h" +#include "StagefrightRecorder.h" + namespace android { const char* cameraPermission = "android.permission.CAMERA"; @@ -286,7 +289,18 @@ MediaRecorderClient::MediaRecorderClient(const sp<MediaPlayerService>& service, { LOGV("Client constructor"); mPid = pid; - mRecorder = new PVMediaRecorder(); + +#if BUILD_WITH_FULL_STAGEFRIGHT + char value[PROPERTY_VALUE_MAX]; + if (property_get("media.stagefright.enable-record", value, NULL) + && (!strcmp(value, "1") || !strcasecmp(value, "true"))) { + mRecorder = new StagefrightRecorder; + } else +#endif + { + mRecorder = new PVMediaRecorder(); + } + mMediaPlayerService = service; } diff --git a/media/libmediaplayerservice/MediaRecorderClient.h b/media/libmediaplayerservice/MediaRecorderClient.h index 6260441..e07306b 100644 --- a/media/libmediaplayerservice/MediaRecorderClient.h +++ b/media/libmediaplayerservice/MediaRecorderClient.h @@ -22,8 +22,7 @@ namespace android { -class PVMediaRecorder; -class ISurface; +class MediaRecorderBase; class MediaPlayerService; class MediaRecorderClient : public BnMediaRecorder @@ -59,7 +58,7 @@ private: pid_t mPid; Mutex mLock; - PVMediaRecorder *mRecorder; + MediaRecorderBase *mRecorder; sp<MediaPlayerService> mMediaPlayerService; }; diff --git a/media/libmediaplayerservice/MetadataRetrieverClient.cpp b/media/libmediaplayerservice/MetadataRetrieverClient.cpp index 2cdc351..866c7bd 100644 --- a/media/libmediaplayerservice/MetadataRetrieverClient.cpp +++ b/media/libmediaplayerservice/MetadataRetrieverClient.cpp @@ -38,6 +38,7 @@ #include "VorbisMetadataRetriever.h" #include "MidiMetadataRetriever.h" #include "MetadataRetrieverClient.h" +#include "StagefrightMetadataRetriever.h" /* desktop Linux needs a little help with gettid() */ #if defined(HAVE_GETTID) && !defined(HAVE_ANDROID_OS) @@ -118,9 +119,15 @@ static sp<MediaMetadataRetrieverBase> createRetriever(player_type playerType) LOGV("create midi metadata retriever"); p = new MidiMetadataRetriever(); break; +#if BUILD_WITH_FULL_STAGEFRIGHT + case STAGEFRIGHT_PLAYER: + LOGV("create StagefrightMetadataRetriever"); + p = new StagefrightMetadataRetriever; + break; +#endif default: // TODO: - // support for STAGEFRIGHT_PLAYER and TEST_PLAYER + // support for TEST_PLAYER LOGE("player type %d is not supported", playerType); break; } @@ -138,12 +145,6 @@ status_t MetadataRetrieverClient::setDataSource(const char *url) return UNKNOWN_ERROR; } player_type playerType = getPlayerType(url); -#if !defined(NO_OPENCORE) && defined(BUILD_WITH_FULL_STAGEFRIGHT) - if (playerType == STAGEFRIGHT_PLAYER) { - // Stagefright doesn't support metadata in this branch yet. - playerType = PV_PLAYER; - } -#endif LOGV("player type = %d", playerType); sp<MediaMetadataRetrieverBase> p = createRetriever(playerType); if (p == NULL) return NO_INIT; @@ -182,12 +183,6 @@ status_t MetadataRetrieverClient::setDataSource(int fd, int64_t offset, int64_t } player_type playerType = getPlayerType(fd, offset, length); -#if !defined(NO_OPENCORE) && defined(BUILD_WITH_FULL_STAGEFRIGHT) - if (playerType == STAGEFRIGHT_PLAYER) { - // Stagefright doesn't support metadata in this branch yet. - playerType = PV_PLAYER; - } -#endif LOGV("player type = %d", playerType); sp<MediaMetadataRetrieverBase> p = createRetriever(playerType); if (p == NULL) { diff --git a/media/libmediaplayerservice/StagefrightMetadataRetriever.cpp b/media/libmediaplayerservice/StagefrightMetadataRetriever.cpp new file mode 100644 index 0000000..7a3aee8 --- /dev/null +++ b/media/libmediaplayerservice/StagefrightMetadataRetriever.cpp @@ -0,0 +1,197 @@ +/* +** +** 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. +*/ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "StagefrightMetadataRetriever" +#include <utils/Log.h> + +#include "StagefrightMetadataRetriever.h" + +#include <media/stagefright/CachingDataSource.h> +#include <media/stagefright/ColorConverter.h> +#include <media/stagefright/DataSource.h> +#include <media/stagefright/HTTPDataSource.h> +#include <media/stagefright/MediaDebug.h> +#include <media/stagefright/MediaExtractor.h> +#include <media/stagefright/MetaData.h> +#include <media/stagefright/MmapSource.h> +#include <media/stagefright/OMXCodec.h> + +namespace android { + +StagefrightMetadataRetriever::StagefrightMetadataRetriever() { + LOGV("StagefrightMetadataRetriever()"); + + DataSource::RegisterDefaultSniffers(); + CHECK_EQ(mClient.connect(), OK); +} + +StagefrightMetadataRetriever::~StagefrightMetadataRetriever() { + LOGV("~StagefrightMetadataRetriever()"); + mClient.disconnect(); +} + +status_t StagefrightMetadataRetriever::setDataSource(const char *uri) { + LOGV("setDataSource(%s)", uri); + + mExtractor = MediaExtractor::CreateFromURI(uri); + + return mExtractor.get() != NULL ? OK : UNKNOWN_ERROR; +} + +status_t StagefrightMetadataRetriever::setDataSource( + int fd, int64_t offset, int64_t length) { + LOGV("setDataSource(%d, %lld, %lld)", fd, offset, length); + + mExtractor = MediaExtractor::Create( + new MmapSource(fd, offset, length)); + + return OK; +} + +VideoFrame *StagefrightMetadataRetriever::captureFrame() { + LOGV("captureFrame"); + + if (mExtractor.get() == NULL) { + LOGV("no extractor."); + return NULL; + } + + size_t n = mExtractor->countTracks(); + size_t i; + for (i = 0; i < n; ++i) { + sp<MetaData> meta = mExtractor->getTrackMetaData(i); + + const char *mime; + CHECK(meta->findCString(kKeyMIMEType, &mime)); + + if (!strncasecmp(mime, "video/", 6)) { + break; + } + } + + if (i == n) { + LOGV("no video track found."); + return NULL; + } + + sp<MetaData> trackMeta = mExtractor->getTrackMetaData( + i, MediaExtractor::kIncludeExtensiveMetaData); + + sp<MediaSource> source = mExtractor->getTrack(i); + + if (source.get() == NULL) { + LOGV("unable to instantiate video track."); + return NULL; + } + + sp<MetaData> meta = source->getFormat(); + + sp<MediaSource> decoder = + OMXCodec::Create( + mClient.interface(), meta, false, source, + NULL, OMXCodec::kPreferSoftwareCodecs); + + if (decoder.get() == NULL) { + LOGV("unable to instantiate video decoder."); + + return NULL; + } + + decoder->start(); + + // Read one output buffer, ignore format change notifications + // and spurious empty buffers. + + MediaSource::ReadOptions options; + int64_t thumbNailTime; + if (trackMeta->findInt64(kKeyThumbnailTime, &thumbNailTime)) { + options.setSeekTo(thumbNailTime); + } + + MediaBuffer *buffer = NULL; + status_t err; + do { + if (buffer != NULL) { + buffer->release(); + buffer = NULL; + } + err = decoder->read(&buffer, &options); + options.clearSeekTo(); + } while (err == INFO_FORMAT_CHANGED + || (buffer != NULL && buffer->range_length() == 0)); + + if (err != OK) { + CHECK_EQ(buffer, NULL); + + LOGV("decoding frame failed."); + decoder->stop(); + + return NULL; + } + + LOGV("successfully decoded video frame."); + + meta = decoder->getFormat(); + + int32_t width, height; + CHECK(meta->findInt32(kKeyWidth, &width)); + CHECK(meta->findInt32(kKeyHeight, &height)); + + VideoFrame *frame = new VideoFrame; + frame->mWidth = width; + frame->mHeight = height; + frame->mDisplayWidth = width; + frame->mDisplayHeight = height; + frame->mSize = width * height * 2; + frame->mData = new uint8_t[frame->mSize]; + + int32_t srcFormat; + CHECK(meta->findInt32(kKeyColorFormat, &srcFormat)); + + ColorConverter converter( + (OMX_COLOR_FORMATTYPE)srcFormat, OMX_COLOR_Format16bitRGB565); + CHECK(converter.isValid()); + + converter.convert( + width, height, + (const uint8_t *)buffer->data() + buffer->range_offset(), + 0, + frame->mData, width * 2); + + buffer->release(); + buffer = NULL; + + decoder->stop(); + + return frame; +} + +MediaAlbumArt *StagefrightMetadataRetriever::extractAlbumArt() { + LOGV("extractAlbumArt (extractor: %s)", mExtractor.get() != NULL ? "YES" : "NO"); + + return NULL; +} + +const char *StagefrightMetadataRetriever::extractMetadata(int keyCode) { + LOGV("extractMetadata %d (extractor: %s)", + keyCode, mExtractor.get() != NULL ? "YES" : "NO"); + + return NULL; +} + +} // namespace android diff --git a/media/libmediaplayerservice/StagefrightMetadataRetriever.h b/media/libmediaplayerservice/StagefrightMetadataRetriever.h new file mode 100644 index 0000000..16127d7 --- /dev/null +++ b/media/libmediaplayerservice/StagefrightMetadataRetriever.h @@ -0,0 +1,53 @@ +/* +** +** 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 STAGEFRIGHT_METADATA_RETRIEVER_H_ + +#define STAGEFRIGHT_METADATA_RETRIEVER_H_ + +#include <media/MediaMetadataRetrieverInterface.h> + +#include <media/stagefright/OMXClient.h> + +namespace android { + +class MediaExtractor; + +struct StagefrightMetadataRetriever : public MediaMetadataRetrieverInterface { + StagefrightMetadataRetriever(); + virtual ~StagefrightMetadataRetriever(); + + virtual status_t setDataSource(const char *url); + virtual status_t setDataSource(int fd, int64_t offset, int64_t length); + + virtual VideoFrame *captureFrame(); + virtual MediaAlbumArt *extractAlbumArt(); + virtual const char *extractMetadata(int keyCode); + +private: + OMXClient mClient; + sp<MediaExtractor> mExtractor; + + StagefrightMetadataRetriever(const StagefrightMetadataRetriever &); + + StagefrightMetadataRetriever &operator=( + const StagefrightMetadataRetriever &); +}; + +} // namespace android + +#endif // STAGEFRIGHT_METADATA_RETRIEVER_H_ diff --git a/media/libmediaplayerservice/StagefrightRecorder.cpp b/media/libmediaplayerservice/StagefrightRecorder.cpp new file mode 100644 index 0000000..a55273d --- /dev/null +++ b/media/libmediaplayerservice/StagefrightRecorder.cpp @@ -0,0 +1,241 @@ +/* + * 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 "StagefrightRecorder" +#include <utils/Log.h> + +#include "StagefrightRecorder.h" + +#include <media/stagefright/CameraSource.h> +#include <media/stagefright/MPEG4Writer.h> +#include <media/stagefright/MediaDebug.h> +#include <media/stagefright/MediaDefs.h> +#include <media/stagefright/MetaData.h> +#include <media/stagefright/OMXClient.h> +#include <media/stagefright/OMXCodec.h> +#include <ui/ICamera.h> +#include <ui/ISurface.h> +#include <utils/Errors.h> + +namespace android { + +StagefrightRecorder::StagefrightRecorder() { + reset(); +} + +StagefrightRecorder::~StagefrightRecorder() { + stop(); + + if (mOutputFd >= 0) { + ::close(mOutputFd); + mOutputFd = -1; + } +} + +status_t StagefrightRecorder::init() { + return OK; +} + +status_t StagefrightRecorder::setAudioSource(audio_source as) { + mAudioSource = as; + + return OK; +} + +status_t StagefrightRecorder::setVideoSource(video_source vs) { + mVideoSource = vs; + + return OK; +} + +status_t StagefrightRecorder::setOutputFormat(output_format of) { + mOutputFormat = of; + + return OK; +} + +status_t StagefrightRecorder::setAudioEncoder(audio_encoder ae) { + mAudioEncoder = ae; + + return OK; +} + +status_t StagefrightRecorder::setVideoEncoder(video_encoder ve) { + mVideoEncoder = ve; + + return OK; +} + +status_t StagefrightRecorder::setVideoSize(int width, int height) { + mVideoWidth = width; + mVideoHeight = height; + + return OK; +} + +status_t StagefrightRecorder::setVideoFrameRate(int frames_per_second) { + mFrameRate = frames_per_second; + + return OK; +} + +status_t StagefrightRecorder::setCamera(const sp<ICamera> &camera) { + mCamera = camera; + + return OK; +} + +status_t StagefrightRecorder::setPreviewSurface(const sp<ISurface> &surface) { + mPreviewSurface = surface; + + return OK; +} + +status_t StagefrightRecorder::setOutputFile(const char *path) { + // We don't actually support this at all, as the media_server process + // no longer has permissions to create files. + + return UNKNOWN_ERROR; +} + +status_t StagefrightRecorder::setOutputFile(int fd, int64_t offset, int64_t length) { + // These don't make any sense, do they? + CHECK_EQ(offset, 0); + CHECK_EQ(length, 0); + + if (mOutputFd >= 0) { + ::close(mOutputFd); + } + mOutputFd = dup(fd); + + return OK; +} + +status_t StagefrightRecorder::setParameters(const String8 ¶ms) { + mParams = params; + + return OK; +} + +status_t StagefrightRecorder::setListener(const sp<IMediaPlayerClient> &listener) { + mListener = listener; + + return OK; +} + +status_t StagefrightRecorder::prepare() { + return OK; +} + +status_t StagefrightRecorder::start() { + if (mWriter != NULL) { + return UNKNOWN_ERROR; + } + + if (mVideoSource == VIDEO_SOURCE_CAMERA) { + CHECK(mCamera != NULL); + + sp<CameraSource> cameraSource = + CameraSource::CreateFromICamera(mCamera); + + CHECK(cameraSource != NULL); + + cameraSource->setPreviewSurface(mPreviewSurface); + + sp<MetaData> enc_meta = new MetaData; + switch (mVideoEncoder) { + case VIDEO_ENCODER_H263: + enc_meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_H263); + break; + + case VIDEO_ENCODER_MPEG_4_SP: + enc_meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_MPEG4); + break; + + case VIDEO_ENCODER_H264: + enc_meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_AVC); + break; + + default: + CHECK(!"Should not be here, unsupported video encoding."); + break; + } + + sp<MetaData> meta = cameraSource->getFormat(); + + int32_t width, height; + CHECK(meta->findInt32(kKeyWidth, &width)); + CHECK(meta->findInt32(kKeyHeight, &height)); + + enc_meta->setInt32(kKeyWidth, width); + enc_meta->setInt32(kKeyHeight, height); + + OMXClient client; + CHECK_EQ(client.connect(), OK); + + sp<MediaSource> encoder = + OMXCodec::Create( + client.interface(), enc_meta, + true /* createEncoder */, cameraSource); + + CHECK(mOutputFd >= 0); + mWriter = new MPEG4Writer(dup(mOutputFd)); + mWriter->addSource(encoder); + mWriter->start(); + } + + return OK; +} + +status_t StagefrightRecorder::stop() { + if (mWriter == NULL) { + return UNKNOWN_ERROR; + } + + mWriter->stop(); + mWriter = NULL; + + return OK; +} + +status_t StagefrightRecorder::close() { + stop(); + + return OK; +} + +status_t StagefrightRecorder::reset() { + stop(); + + mAudioSource = AUDIO_SOURCE_LIST_END; + mVideoSource = VIDEO_SOURCE_LIST_END; + mOutputFormat = OUTPUT_FORMAT_LIST_END; + mAudioEncoder = AUDIO_ENCODER_LIST_END; + mVideoEncoder = VIDEO_ENCODER_LIST_END; + mVideoWidth = -1; + mVideoHeight = -1; + mFrameRate = -1; + mOutputFd = -1; + + return OK; +} + +status_t StagefrightRecorder::getMaxAmplitude(int *max) { + return UNKNOWN_ERROR; +} + +} // namespace android diff --git a/media/libmediaplayerservice/StagefrightRecorder.h b/media/libmediaplayerservice/StagefrightRecorder.h new file mode 100644 index 0000000..56c4e0e --- /dev/null +++ b/media/libmediaplayerservice/StagefrightRecorder.h @@ -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. + */ + +#ifndef STAGEFRIGHT_RECORDER_H_ + +#define STAGEFRIGHT_RECORDER_H_ + +#include <media/MediaRecorderBase.h> +#include <utils/String8.h> + +namespace android { + +class MPEG4Writer; + +struct StagefrightRecorder : public MediaRecorderBase { + StagefrightRecorder(); + virtual ~StagefrightRecorder(); + + virtual status_t init(); + virtual status_t setAudioSource(audio_source as); + virtual status_t setVideoSource(video_source vs); + virtual status_t setOutputFormat(output_format of); + virtual status_t setAudioEncoder(audio_encoder ae); + virtual status_t setVideoEncoder(video_encoder ve); + virtual status_t setVideoSize(int width, int height); + virtual status_t setVideoFrameRate(int frames_per_second); + virtual status_t setCamera(const sp<ICamera>& camera); + virtual status_t setPreviewSurface(const sp<ISurface>& surface); + virtual status_t setOutputFile(const char *path); + virtual status_t setOutputFile(int fd, int64_t offset, int64_t length); + virtual status_t setParameters(const String8& params); + virtual status_t setListener(const sp<IMediaPlayerClient>& listener); + virtual status_t prepare(); + virtual status_t start(); + virtual status_t stop(); + virtual status_t close(); + virtual status_t reset(); + virtual status_t getMaxAmplitude(int *max); + +private: + sp<ICamera> mCamera; + sp<ISurface> mPreviewSurface; + sp<IMediaPlayerClient> mListener; + sp<MPEG4Writer> mWriter; + + audio_source mAudioSource; + video_source mVideoSource; + output_format mOutputFormat; + audio_encoder mAudioEncoder; + video_encoder mVideoEncoder; + int mVideoWidth, mVideoHeight; + int mFrameRate; + String8 mParams; + int mOutputFd; + + StagefrightRecorder(const StagefrightRecorder &); + StagefrightRecorder &operator=(const StagefrightRecorder &); +}; + +} // namespace android + +#endif // STAGEFRIGHT_RECORDER_H_ + diff --git a/media/libmediaplayerservice/TestPlayerStub.cpp b/media/libmediaplayerservice/TestPlayerStub.cpp index 8627708..aa49429 100644 --- a/media/libmediaplayerservice/TestPlayerStub.cpp +++ b/media/libmediaplayerservice/TestPlayerStub.cpp @@ -176,7 +176,7 @@ status_t TestPlayerStub::resetInternal() mContentUrl = NULL; if (mPlayer) { - LOG_ASSERT(mDeletePlayer != NULL); + LOG_ASSERT(mDeletePlayer != NULL, "mDeletePlayer is null"); (*mDeletePlayer)(mPlayer); mPlayer = NULL; } diff --git a/media/libstagefright/AMRExtractor.cpp b/media/libstagefright/AMRExtractor.cpp index 8d85ce2..1e3c5a4 100644 --- a/media/libstagefright/AMRExtractor.cpp +++ b/media/libstagefright/AMRExtractor.cpp @@ -18,7 +18,8 @@ #define LOG_TAG "AMRExtractor" #include <utils/Log.h> -#include <media/stagefright/AMRExtractor.h> +#include "include/AMRExtractor.h" + #include <media/stagefright/DataSource.h> #include <media/stagefright/MediaBufferGroup.h> #include <media/stagefright/MediaDebug.h> @@ -86,7 +87,7 @@ sp<MediaSource> AMRExtractor::getTrack(size_t index) { return new AMRSource(mDataSource, mIsWide); } -sp<MetaData> AMRExtractor::getTrackMetaData(size_t index) { +sp<MetaData> AMRExtractor::getTrackMetaData(size_t index, uint32_t flags) { if (mInitCheck != OK || index != 0) { return NULL; } @@ -155,7 +156,7 @@ status_t AMRSource::read( *out = NULL; uint8_t header; - ssize_t n = mDataSource->read_at(mOffset, &header, 1); + ssize_t n = mDataSource->readAt(mOffset, &header, 1); if (n < 1) { return ERROR_IO; @@ -191,7 +192,7 @@ status_t AMRSource::read( // Round up bits to bytes and add 1 for the header byte. frameSize = (frameSize + 7) / 8 + 1; - n = mDataSource->read_at(mOffset, buffer->data(), frameSize); + n = mDataSource->readAt(mOffset, buffer->data(), frameSize); if (n != (ssize_t)frameSize) { buffer->release(); @@ -201,10 +202,7 @@ status_t AMRSource::read( } buffer->set_range(0, frameSize); - buffer->meta_data()->setInt32( - kKeyTimeUnits, (mCurrentTimeUs + 500) / 1000); - buffer->meta_data()->setInt32( - kKeyTimeScale, 1000); + buffer->meta_data()->setInt64(kKeyTime, mCurrentTimeUs); mOffset += frameSize; mCurrentTimeUs += 20000; // Each frame is 20ms @@ -220,7 +218,7 @@ bool SniffAMR( const sp<DataSource> &source, String8 *mimeType, float *confidence) { char header[9]; - if (source->read_at(0, header, sizeof(header)) != sizeof(header)) { + if (source->readAt(0, header, sizeof(header)) != sizeof(header)) { return false; } diff --git a/media/libstagefright/Android.mk b/media/libstagefright/Android.mk index 9f71dae..c36e769 100644 --- a/media/libstagefright/Android.mk +++ b/media/libstagefright/Android.mk @@ -16,24 +16,26 @@ ifeq ($(BUILD_WITH_FULL_STAGEFRIGHT),true) LOCAL_SRC_FILES += \ AMRExtractor.cpp \ + AudioPlayer.cpp \ CachingDataSource.cpp \ + CameraSource.cpp \ DataSource.cpp \ FileSource.cpp \ HTTPDataSource.cpp \ HTTPStream.cpp \ JPEGSource.cpp \ - MediaExtractor.cpp \ MP3Extractor.cpp \ MPEG4Extractor.cpp \ MPEG4Writer.cpp \ + MediaExtractor.cpp \ MediaPlayerImpl.cpp \ MmapSource.cpp \ SampleTable.cpp \ ShoutcastSource.cpp \ TimeSource.cpp \ TimedEventQueue.cpp \ - AudioPlayer.cpp \ - stagefright_string.cpp + WAVExtractor.cpp \ + string.cpp endif diff --git a/media/libstagefright/AudioPlayer.cpp b/media/libstagefright/AudioPlayer.cpp index 538facb..d7e3f66 100644 --- a/media/libstagefright/AudioPlayer.cpp +++ b/media/libstagefright/AudioPlayer.cpp @@ -210,15 +210,9 @@ void AudioPlayer::fillBuffer(void *data, size_t size) { break; } - int32_t units, scale; - bool success = - mInputBuffer->meta_data()->findInt32(kKeyTimeUnits, &units); - success = success && - mInputBuffer->meta_data()->findInt32(kKeyTimeScale, &scale); - CHECK(success); - Mutex::Autolock autoLock(mLock); - mPositionTimeMediaUs = (int64_t)units * 1000000 / scale; + CHECK(mInputBuffer->meta_data()->findInt64( + kKeyTime, &mPositionTimeMediaUs)); mPositionTimeRealUs = ((mNumFramesPlayed + size_done / mFrameSize) * 1000000) diff --git a/media/libstagefright/CachingDataSource.cpp b/media/libstagefright/CachingDataSource.cpp index fd00576..23f4897 100644 --- a/media/libstagefright/CachingDataSource.cpp +++ b/media/libstagefright/CachingDataSource.cpp @@ -61,11 +61,11 @@ CachingDataSource::~CachingDataSource() { mData = NULL; } -status_t CachingDataSource::InitCheck() const { - return OK; +status_t CachingDataSource::initCheck() const { + return mSource->initCheck(); } -ssize_t CachingDataSource::read_at(off_t offset, void *data, size_t size) { +ssize_t CachingDataSource::readAt(off_t offset, void *data, size_t size) { Mutex::Autolock autoLock(mLock); size_t total = 0; @@ -82,7 +82,7 @@ ssize_t CachingDataSource::read_at(off_t offset, void *data, size_t size) { if (page == NULL) { page = allocate_page(); page->mOffset = offset - offset % mPageSize; - ssize_t n = mSource->read_at(page->mOffset, page->mData, mPageSize); + ssize_t n = mSource->readAt(page->mOffset, page->mData, mPageSize); if (n < 0) { page->mLength = 0; } else { diff --git a/media/libstagefright/CameraSource.cpp b/media/libstagefright/CameraSource.cpp index 596ab67..bd862e0 100644 --- a/media/libstagefright/CameraSource.cpp +++ b/media/libstagefright/CameraSource.cpp @@ -19,122 +19,168 @@ #include <OMX_Component.h> #include <binder/IServiceManager.h> +#include <cutils/properties.h> // for property_get #include <media/stagefright/CameraSource.h> #include <media/stagefright/MediaDebug.h> +#include <media/stagefright/MediaDefs.h> #include <media/stagefright/MediaErrors.h> #include <media/stagefright/MetaData.h> -#include <ui/ICameraClient.h> -#include <ui/ICameraService.h> +#include <ui/Camera.h> +#include <ui/CameraParameters.h> +#include <ui/GraphicBuffer.h> +#include <ui/ISurface.h> #include <ui/Overlay.h> -#include <utils/String16.h> +#include <utils/String8.h> namespace android { -class CameraBuffer : public MediaBuffer { -public: - CameraBuffer(const sp<IMemory> &frame) - : MediaBuffer(frame->pointer(), frame->size()), - mFrame(frame) { - } +static int64_t getNowUs() { + struct timeval tv; + gettimeofday(&tv, NULL); - sp<IMemory> releaseFrame() { - sp<IMemory> frame = mFrame; - mFrame.clear(); - return frame; - } + return (int64_t)tv.tv_usec + tv.tv_sec * 1000000; +} -private: - sp<IMemory> mFrame; -}; +struct DummySurface : public BnSurface { + DummySurface() {} -class CameraSourceClient : public BnCameraClient { -public: - CameraSourceClient() - : mSource(NULL) { + virtual sp<GraphicBuffer> requestBuffer(int bufferIdx, int usage) { + return NULL; } - virtual void notifyCallback(int32_t msgType, int32_t ext1, int32_t ext2) { - CHECK(mSource != NULL); - mSource->notifyCallback(msgType, ext1, ext2); + virtual status_t registerBuffers(const BufferHeap &buffers) { + return OK; } - virtual void dataCallback(int32_t msgType, const sp<IMemory> &data) { - CHECK(mSource != NULL); - mSource->dataCallback(msgType, data); - } + virtual void postBuffer(ssize_t offset) {} + virtual void unregisterBuffers() {} - void setCameraSource(CameraSource *source) { - mSource = source; + virtual sp<OverlayRef> createOverlay( + uint32_t w, uint32_t h, int32_t format) { + return NULL; } +protected: + virtual ~DummySurface() {} + + DummySurface(const DummySurface &); + DummySurface &operator=(const DummySurface &); +}; + +struct CameraSourceListener : public CameraListener { + CameraSourceListener(const sp<CameraSource> &source); + + virtual void notify(int32_t msgType, int32_t ext1, int32_t ext2); + virtual void postData(int32_t msgType, const sp<IMemory> &dataPtr); + + virtual void postDataTimestamp( + nsecs_t timestamp, int32_t msgType, const sp<IMemory>& dataPtr); + +protected: + virtual ~CameraSourceListener(); + private: - CameraSource *mSource; + wp<CameraSource> mSource; + + CameraSourceListener(const CameraSourceListener &); + CameraSourceListener &operator=(const CameraSourceListener &); }; -class DummySurface : public BnSurface { -public: - DummySurface() {} +CameraSourceListener::CameraSourceListener(const sp<CameraSource> &source) + : mSource(source) { +} - virtual status_t registerBuffers(const BufferHeap &buffers) { - return OK; - } +CameraSourceListener::~CameraSourceListener() { +} - virtual void postBuffer(ssize_t offset) { - } +void CameraSourceListener::notify(int32_t msgType, int32_t ext1, int32_t ext2) { + LOGV("notify(%d, %d, %d)", msgType, ext1, ext2); +} - virtual void unregisterBuffers() { - } - - virtual sp<OverlayRef> createOverlay( - uint32_t w, uint32_t h, int32_t format) { - return NULL; +void CameraSourceListener::postData(int32_t msgType, const sp<IMemory> &dataPtr) { + LOGV("postData(%d, ptr:%p, size:%d)", + msgType, dataPtr->pointer(), dataPtr->size()); + + sp<CameraSource> source = mSource.promote(); + if (source.get() != NULL) { + source->dataCallback(msgType, dataPtr); } -}; +} + +void CameraSourceListener::postDataTimestamp( + nsecs_t timestamp, int32_t msgType, const sp<IMemory>& dataPtr) { + LOGV("postDataTimestamp(%lld, %d, ptr:%p, size:%d)", + timestamp, msgType, dataPtr->pointer(), dataPtr->size()); +} // static CameraSource *CameraSource::Create() { - sp<IServiceManager> sm = defaultServiceManager(); + sp<Camera> camera = Camera::connect(); + + if (camera.get() == NULL) { + return NULL; + } - sp<ICameraService> service = - interface_cast<ICameraService>( - sm->getService(String16("media.camera"))); + return new CameraSource(camera); +} - sp<CameraSourceClient> client = new CameraSourceClient; - sp<ICamera> camera = service->connect(client); +// static +CameraSource *CameraSource::CreateFromICamera(const sp<ICamera> &icamera) { + sp<Camera> camera = Camera::create(icamera); - CameraSource *source = new CameraSource(camera, client); - client->setCameraSource(source); + if (camera.get() == NULL) { + return NULL; + } - return source; + return new CameraSource(camera); } -CameraSource::CameraSource( - const sp<ICamera> &camera, const sp<ICameraClient> &client) +CameraSource::CameraSource(const sp<Camera> &camera) : mCamera(camera), - mCameraClient(client), + mWidth(0), + mHeight(0), + mFirstFrameTimeUs(0), mNumFrames(0), mStarted(false) { - printf("params: \"%s\"\n", mCamera->getParameters().string()); + char value[PROPERTY_VALUE_MAX]; + if (property_get("ro.hardware", value, NULL) && !strcmp(value, "sholes")) { + // The hardware encoder(s) do not support yuv420, but only YCbYCr, + // fortunately the camera also supports this, so we needn't transcode. + mCamera->setParameters(String8("preview-format=yuv422i-yuyv")); + } + + String8 s = mCamera->getParameters(); + printf("params: \"%s\"\n", s.string()); + + CameraParameters params(s); + params.getPreviewSize(&mWidth, &mHeight); } CameraSource::~CameraSource() { if (mStarted) { stop(); } +} - mCamera->disconnect(); +void CameraSource::setPreviewSurface(const sp<ISurface> &surface) { + mPreviewSurface = surface; } status_t CameraSource::start(MetaData *) { CHECK(!mStarted); - status_t err = mCamera->lock(); - CHECK_EQ(err, OK); + mCamera->setListener(new CameraSourceListener(this)); - err = mCamera->setPreviewDisplay(new DummySurface); + status_t err = + mCamera->setPreviewDisplay( + mPreviewSurface != NULL ? mPreviewSurface : new DummySurface); CHECK_EQ(err, OK); - mCamera->setPreviewCallbackFlag(1); - mCamera->startPreview(); + + mCamera->setPreviewCallbackFlags( + FRAME_CALLBACK_FLAG_ENABLE_MASK + | FRAME_CALLBACK_FLAG_COPY_OUT_MASK); + + err = mCamera->startPreview(); CHECK_EQ(err, OK); mStarted = true; @@ -146,7 +192,6 @@ status_t CameraSource::stop() { CHECK(mStarted); mCamera->stopPreview(); - mCamera->unlock(); mStarted = false; @@ -157,8 +202,8 @@ sp<MetaData> CameraSource::getFormat() { sp<MetaData> meta = new MetaData; meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_RAW); meta->setInt32(kKeyColorFormat, OMX_COLOR_FormatYUV420SemiPlanar); - meta->setInt32(kKeyWidth, 480); - meta->setInt32(kKeyHeight, 320); + meta->setInt32(kKeyWidth, mWidth); + meta->setInt32(kKeyHeight, mHeight); return meta; } @@ -175,6 +220,7 @@ status_t CameraSource::read( } sp<IMemory> frame; + int64_t frameTime; { Mutex::Autolock autoLock(mLock); @@ -184,41 +230,33 @@ status_t CameraSource::read( frame = *mFrames.begin(); mFrames.erase(mFrames.begin()); - } - int count = mNumFrames++; + frameTime = *mFrameTimes.begin(); + mFrameTimes.erase(mFrameTimes.begin()); + } - *buffer = new CameraBuffer(frame); + *buffer = new MediaBuffer(frame->size()); + memcpy((*buffer)->data(), frame->pointer(), frame->size()); + (*buffer)->set_range(0, frame->size()); (*buffer)->meta_data()->clear(); - (*buffer)->meta_data()->setInt32(kKeyTimeScale, 15); - (*buffer)->meta_data()->setInt32(kKeyTimeUnits, count); - - (*buffer)->add_ref(); - (*buffer)->setObserver(this); + (*buffer)->meta_data()->setInt64(kKeyTime, frameTime); 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); + int64_t nowUs = getNowUs(); + if (mNumFrames == 0) { + mFirstFrameTimeUs = nowUs; + } + ++mNumFrames; + mFrames.push_back(data); + mFrameTimes.push_back(nowUs - mFirstFrameTimeUs); 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 index daac539..2a6dbc4 100644 --- a/media/libstagefright/DataSource.cpp +++ b/media/libstagefright/DataSource.cpp @@ -14,11 +14,13 @@ * limitations under the License. */ -#include <media/stagefright/AMRExtractor.h> +#include "include/AMRExtractor.h" +#include "include/MP3Extractor.h" +#include "include/MPEG4Extractor.h" +#include "include/WAVExtractor.h" + #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 { @@ -27,7 +29,7 @@ bool DataSource::getUInt16(off_t offset, uint16_t *x) { *x = 0; uint8_t byte[2]; - if (read_at(offset, byte, 2) != 2) { + if (readAt(offset, byte, 2) != 2) { return false; } @@ -86,6 +88,7 @@ void DataSource::RegisterDefaultSniffers() { RegisterSniffer(SniffMP3); RegisterSniffer(SniffMPEG4); RegisterSniffer(SniffAMR); + RegisterSniffer(SniffWAV); } } // namespace android diff --git a/media/libstagefright/ESDS.cpp b/media/libstagefright/ESDS.cpp index 53b92a0..28d338c 100644 --- a/media/libstagefright/ESDS.cpp +++ b/media/libstagefright/ESDS.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include <media/stagefright/ESDS.h> +#include "include/ESDS.h" #include <string.h> diff --git a/media/libstagefright/FileSource.cpp b/media/libstagefright/FileSource.cpp index f6b90b2..130239e 100644 --- a/media/libstagefright/FileSource.cpp +++ b/media/libstagefright/FileSource.cpp @@ -20,7 +20,17 @@ namespace android { FileSource::FileSource(const char *filename) - : mFile(fopen(filename, "rb")) { + : mFile(fopen(filename, "rb")), + mOffset(0), + mLength(-1) { +} + +FileSource::FileSource(int fd, int64_t offset, int64_t length) + : mFile(fdopen(fd, "rb")), + mOffset(offset), + mLength(length) { + CHECK(offset >= 0); + CHECK(length >= 0); } FileSource::~FileSource() { @@ -30,19 +40,40 @@ FileSource::~FileSource() { } } -status_t FileSource::InitCheck() const { +status_t FileSource::initCheck() const { return mFile != NULL ? OK : NO_INIT; } -ssize_t FileSource::read_at(off_t offset, void *data, size_t size) { +ssize_t FileSource::readAt(off_t offset, void *data, size_t size) { Mutex::Autolock autoLock(mLock); - int err = fseeko(mFile, offset, SEEK_SET); + if (mLength >= 0) { + if (offset >= mLength) { + return 0; // read beyond EOF. + } + int64_t numAvailable = mLength - offset; + if ((int64_t)size > numAvailable) { + size = numAvailable; + } + } + + int err = fseeko(mFile, offset + mOffset, SEEK_SET); CHECK(err != -1); - ssize_t result = fread(data, 1, size, mFile); + return fread(data, 1, size, mFile); +} + +status_t FileSource::getSize(off_t *size) { + if (mLength >= 0) { + *size = mLength; + + return OK; + } + + fseek(mFile, SEEK_END, 0); + *size = ftello(mFile); - return result; + return OK; } } // namespace android diff --git a/media/libstagefright/HTTPDataSource.cpp b/media/libstagefright/HTTPDataSource.cpp index 4dedebd..5536801 100644 --- a/media/libstagefright/HTTPDataSource.cpp +++ b/media/libstagefright/HTTPDataSource.cpp @@ -14,17 +14,19 @@ * limitations under the License. */ +#include "include/stagefright_string.h" +#include "include/HTTPStream.h" + #include <stdlib.h> #include <media/stagefright/HTTPDataSource.h> -#include <media/stagefright/HTTPStream.h> #include <media/stagefright/MediaDebug.h> -#include <media/stagefright/stagefright_string.h> namespace android { HTTPDataSource::HTTPDataSource(const char *uri) - : mHost(NULL), + : mHttp(new HTTPStream), + mHost(NULL), mPort(0), mPath(NULL), mBuffer(malloc(kBufferSize)), @@ -65,33 +67,40 @@ HTTPDataSource::HTTPDataSource(const char *uri) mPort = port; mPath = strdup(path.c_str()); - status_t err = mHttp.connect(mHost, mPort); - CHECK_EQ(err, OK); + mInitCheck = mHttp->connect(mHost, mPort); } HTTPDataSource::HTTPDataSource(const char *host, int port, const char *path) - : mHost(strdup(host)), + : mHttp(new HTTPStream), + mHost(strdup(host)), mPort(port), mPath(strdup(path)), mBuffer(malloc(kBufferSize)), mBufferLength(0), mBufferOffset(0) { - status_t err = mHttp.connect(mHost, mPort); - CHECK_EQ(err, OK); + mInitCheck = mHttp->connect(mHost, mPort); +} + +status_t HTTPDataSource::initCheck() const { + return mInitCheck; } HTTPDataSource::~HTTPDataSource() { - mHttp.disconnect(); + mHttp->disconnect(); free(mBuffer); mBuffer = NULL; free(mPath); mPath = NULL; + + delete mHttp; + mHttp = NULL; } -ssize_t HTTPDataSource::read_at(off_t offset, void *data, size_t size) { - if (offset >= mBufferOffset && offset < mBufferOffset + mBufferLength) { +ssize_t HTTPDataSource::readAt(off_t offset, void *data, size_t size) { + if (offset >= mBufferOffset + && offset < (off_t)(mBufferOffset + mBufferLength)) { size_t num_bytes_available = mBufferLength - (offset - mBufferOffset); size_t copy = num_bytes_available; @@ -119,19 +128,19 @@ ssize_t HTTPDataSource::read_at(off_t offset, void *data, size_t size) { 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 ((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); + mHttp->connect(mHost, mPort); ++attempt; } else { break; @@ -143,14 +152,14 @@ ssize_t HTTPDataSource::read_at(off_t offset, void *data, size_t size) { } string value; - if (!mHttp.find_header_value("Content-Length", &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); + ssize_t num_bytes_received = mHttp->receive(mBuffer, contentLength); if (num_bytes_received <= 0) { return num_bytes_received; diff --git a/media/libstagefright/HTTPStream.cpp b/media/libstagefright/HTTPStream.cpp index 6af7df9..02f9439 100644 --- a/media/libstagefright/HTTPStream.cpp +++ b/media/libstagefright/HTTPStream.cpp @@ -14,6 +14,8 @@ * limitations under the License. */ +#include "include/HTTPStream.h" + #include <sys/socket.h> #include <arpa/inet.h> @@ -25,7 +27,6 @@ #include <string.h> #include <unistd.h> -#include <media/stagefright/HTTPStream.h> #include <media/stagefright/MediaDebug.h> namespace android { diff --git a/media/libstagefright/JPEGSource.cpp b/media/libstagefright/JPEGSource.cpp index d1dfd83..a4be2dd 100644 --- a/media/libstagefright/JPEGSource.cpp +++ b/media/libstagefright/JPEGSource.cpp @@ -119,7 +119,7 @@ status_t JPEGSource::read( MediaBuffer *buffer; mGroup->acquire_buffer(&buffer); - ssize_t n = mSource->read_at(mOffset, buffer->data(), mSize - mOffset); + ssize_t n = mSource->readAt(mOffset, buffer->data(), mSize - mOffset); if (n <= 0) { buffer->release(); @@ -156,13 +156,13 @@ status_t JPEGSource::parseJPEG() { for (;;) { uint8_t marker; - if (mSource->read_at(i++, &marker, 1) != 1) { + if (mSource->readAt(i++, &marker, 1) != 1) { return ERROR_IO; } CHECK_EQ(marker, 0xff); - if (mSource->read_at(i++, &marker, 1) != 1) { + if (mSource->readAt(i++, &marker, 1) != 1) { return ERROR_IO; } diff --git a/media/libstagefright/MP3Extractor.cpp b/media/libstagefright/MP3Extractor.cpp index 7fd699f..8dd8ea9 100644 --- a/media/libstagefright/MP3Extractor.cpp +++ b/media/libstagefright/MP3Extractor.cpp @@ -18,8 +18,9 @@ #define LOG_TAG "MP3Extractor" #include <utils/Log.h> +#include "include/MP3Extractor.h" + #include <media/stagefright/DataSource.h> -#include <media/stagefright/MP3Extractor.h> #include <media/stagefright/MediaBuffer.h> #include <media/stagefright/MediaBufferGroup.h> #include <media/stagefright/MediaDebug.h> @@ -147,7 +148,12 @@ static bool get_mp3_frame_size( *out_bitrate = bitrate; } - *frame_size = 144000 * bitrate / sampling_rate + padding; + if (version == 3 /* V1 */) { + *frame_size = 144000 * bitrate / sampling_rate + padding; + } else { + // V2 or V2.5 + *frame_size = 72000 * bitrate / sampling_rate + padding; + } } if (out_sampling_rate) { @@ -166,6 +172,33 @@ static bool get_mp3_frame_size( static bool Resync( const sp<DataSource> &source, uint32_t match_header, off_t *inout_pos, uint32_t *out_header) { + if (*inout_pos == 0) { + // Skip an optional ID3 header if syncing at the very beginning + // of the datasource. + + uint8_t id3header[10]; + if (source->readAt(0, id3header, sizeof(id3header)) + < (ssize_t)sizeof(id3header)) { + // If we can't even read these 10 bytes, we might as well bail out, + // even if there _were_ 10 bytes of valid mp3 audio data... + return false; + } + + if (id3header[0] == 'I' && id3header[1] == 'D' && id3header[2] == '3') { + // Skip the ID3v2 header. + + size_t len = + ((id3header[6] & 0x7f) << 21) + | ((id3header[7] & 0x7f) << 14) + | ((id3header[8] & 0x7f) << 7) + | (id3header[9] & 0x7f); + + len += 10; + + *inout_pos += len; + } + } + // Everything must match except for // protection, bitrate, padding, private bits and mode extension. const uint32_t kMask = 0xfffe0ccf; @@ -195,7 +228,7 @@ static bool Resync( buffer_length = buffer_length - buffer_offset; buffer_offset = 0; - ssize_t n = source->read_at( + ssize_t n = source->readAt( pos, &buffer[buffer_length], kMaxFrameSize - buffer_length); if (n <= 0) { @@ -232,7 +265,7 @@ static bool Resync( valid = true; for (int j = 0; j < 3; ++j) { uint8_t tmp[4]; - if (source->read_at(test_pos, tmp, 4) < 4) { + if (source->readAt(test_pos, tmp, 4) < 4) { valid = false; break; } @@ -338,10 +371,9 @@ MP3Extractor::MP3Extractor(const sp<DataSource> &source) off_t fileSize; if (mDataSource->getSize(&fileSize) == OK) { - mMeta->setInt32( + mMeta->setInt64( kKeyDuration, - 8 * (fileSize - mFirstFramePos) / bitrate); - mMeta->setInt32(kKeyTimeScale, 1000); + 8000LL * (fileSize - mFirstFramePos) / bitrate); } } } @@ -362,7 +394,7 @@ sp<MediaSource> MP3Extractor::getTrack(size_t index) { mMeta, mDataSource, mFirstFramePos, mFixedHeader); } -sp<MetaData> MP3Extractor::getTrackMetaData(size_t index) { +sp<MetaData> MP3Extractor::getTrackMetaData(size_t index, uint32_t flags) { if (mFirstFramePos < 0 || index != 0) { return NULL; } @@ -448,7 +480,7 @@ status_t MP3Source::read( size_t frame_size; for (;;) { - ssize_t n = mDataSource->read_at(mCurrentPos, buffer->data(), 4); + ssize_t n = mDataSource->readAt(mCurrentPos, buffer->data(), 4); if (n < 4) { buffer->release(); buffer = NULL; @@ -482,7 +514,7 @@ status_t MP3Source::read( CHECK(frame_size <= buffer->size()); - ssize_t n = mDataSource->read_at(mCurrentPos, buffer->data(), frame_size); + ssize_t n = mDataSource->readAt(mCurrentPos, buffer->data(), frame_size); if (n < (ssize_t)frame_size) { buffer->release(); buffer = NULL; @@ -492,8 +524,7 @@ status_t MP3Source::read( buffer->set_range(0, frame_size); - buffer->meta_data()->setInt32(kKeyTimeUnits, mCurrentTimeUs / 1000); - buffer->meta_data()->setInt32(kKeyTimeScale, 1000); + buffer->meta_data()->setInt64(kKeyTime, mCurrentTimeUs); mCurrentPos += frame_size; mCurrentTimeUs += 1152 * 1000000 / 44100; diff --git a/media/libstagefright/MPEG4Extractor.cpp b/media/libstagefright/MPEG4Extractor.cpp index 9174d19..9d17064 100644 --- a/media/libstagefright/MPEG4Extractor.cpp +++ b/media/libstagefright/MPEG4Extractor.cpp @@ -17,6 +17,9 @@ #define LOG_TAG "MPEG4Extractor" #include <utils/Log.h> +#include "include/MPEG4Extractor.h" +#include "include/SampleTable.h" + #include <arpa/inet.h> #include <ctype.h> @@ -25,14 +28,12 @@ #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/MediaDebug.h> #include <media/stagefright/MediaDefs.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> @@ -43,6 +44,7 @@ public: // Caller retains ownership of both "dataSource" and "sampleTable". MPEG4Source(const sp<MetaData> &format, const sp<DataSource> &dataSource, + int32_t timeScale, const sp<SampleTable> &sampleTable); virtual status_t start(MetaData *params = NULL); @@ -177,7 +179,8 @@ size_t MPEG4Extractor::countTracks() { return n; } -sp<MetaData> MPEG4Extractor::getTrackMetaData(size_t index) { +sp<MetaData> MPEG4Extractor::getTrackMetaData( + size_t index, uint32_t flags) { status_t err; if ((err = readMetaData()) != OK) { return NULL; @@ -197,6 +200,25 @@ sp<MetaData> MPEG4Extractor::getTrackMetaData(size_t index) { return NULL; } + if ((flags & kIncludeExtensiveMetaData) + && !track->includes_expensive_metadata) { + track->includes_expensive_metadata = true; + + const char *mime; + CHECK(track->meta->findCString(kKeyMIMEType, &mime)); + if (!strncasecmp("video/", mime, 6)) { + uint32_t sampleIndex; + uint32_t sampleTime; + if (track->sampleTable->findThumbnailSample(&sampleIndex) == OK + && track->sampleTable->getDecodingTime( + sampleIndex, &sampleTime) == OK) { + track->meta->setInt64( + kKeyThumbnailTime, + ((int64_t)sampleTime * 1000000) / track->timescale); + } + } + } + return track->meta; } @@ -227,7 +249,7 @@ static void MakeFourCCString(uint32_t x, char *s) { status_t MPEG4Extractor::parseChunk(off_t *offset, int depth) { uint32_t hdr[2]; - if (mDataSource->read_at(*offset, hdr, 8) < 8) { + if (mDataSource->readAt(*offset, hdr, 8) < 8) { return ERROR_IO; } uint64_t chunk_size = ntohl(hdr[0]); @@ -235,7 +257,7 @@ status_t MPEG4Extractor::parseChunk(off_t *offset, int depth) { off_t data_offset = *offset + 8; if (chunk_size == 1) { - if (mDataSource->read_at(*offset + 8, &chunk_size, 8) < 8) { + if (mDataSource->readAt(*offset + 8, &chunk_size, 8) < 8) { return ERROR_IO; } chunk_size = ntoh64(chunk_size); @@ -252,7 +274,7 @@ status_t MPEG4Extractor::parseChunk(off_t *offset, int depth) { char buffer[256]; if (chunk_size <= sizeof(buffer)) { - if (mDataSource->read_at(*offset, buffer, chunk_size) < chunk_size) { + if (mDataSource->readAt(*offset, buffer, chunk_size) < chunk_size) { return ERROR_IO; } @@ -298,7 +320,7 @@ status_t MPEG4Extractor::parseChunk(off_t *offset, int depth) { CHECK(chunk_data_size >= 4); uint8_t version; - if (mDataSource->read_at(data_offset, &version, 1) < 1) { + if (mDataSource->readAt(data_offset, &version, 1) < 1) { return ERROR_IO; } @@ -312,7 +334,7 @@ status_t MPEG4Extractor::parseChunk(off_t *offset, int depth) { } uint8_t buffer[36 + 60]; - if (mDataSource->read_at( + if (mDataSource->readAt( data_offset, buffer, sizeof(buffer)) < (ssize_t)sizeof(buffer)) { return ERROR_IO; } @@ -329,7 +351,7 @@ status_t MPEG4Extractor::parseChunk(off_t *offset, int depth) { } uint8_t buffer[24 + 60]; - if (mDataSource->read_at( + if (mDataSource->readAt( data_offset, buffer, sizeof(buffer)) < (ssize_t)sizeof(buffer)) { return ERROR_IO; } @@ -351,6 +373,7 @@ status_t MPEG4Extractor::parseChunk(off_t *offset, int depth) { mLastTrack = track; track->meta = new MetaData; + track->includes_expensive_metadata = false; track->timescale = 0; track->sampleTable = new SampleTable(mDataSource); track->meta->setCString(kKeyMIMEType, "application/octet-stream"); @@ -366,7 +389,7 @@ status_t MPEG4Extractor::parseChunk(off_t *offset, int depth) { } uint8_t version; - if (mDataSource->read_at( + if (mDataSource->readAt( data_offset, &version, sizeof(version)) < (ssize_t)sizeof(version)) { return ERROR_IO; @@ -383,18 +406,17 @@ status_t MPEG4Extractor::parseChunk(off_t *offset, int depth) { } uint32_t timescale; - if (mDataSource->read_at( + if (mDataSource->readAt( 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( + if (mDataSource->readAt( timescale_offset + 4, &duration, sizeof(duration)) < (ssize_t)sizeof(duration)) { return ERROR_IO; @@ -402,14 +424,15 @@ status_t MPEG4Extractor::parseChunk(off_t *offset, int depth) { duration = ntoh64(duration); } else { int32_t duration32; - if (mDataSource->read_at( + if (mDataSource->readAt( timescale_offset + 4, &duration32, sizeof(duration32)) < (ssize_t)sizeof(duration32)) { return ERROR_IO; } duration = ntohl(duration32); } - mLastTrack->meta->setInt32(kKeyDuration, duration); + mLastTrack->meta->setInt64( + kKeyDuration, (duration * 1000000) / mLastTrack->timescale); *offset += chunk_size; break; @@ -422,7 +445,7 @@ status_t MPEG4Extractor::parseChunk(off_t *offset, int depth) { } uint8_t buffer[24]; - if (mDataSource->read_at(data_offset, buffer, 24) < 24) { + if (mDataSource->readAt(data_offset, buffer, 24) < 24) { return ERROR_IO; } @@ -449,7 +472,7 @@ status_t MPEG4Extractor::parseChunk(off_t *offset, int depth) { uint8_t buffer[8]; CHECK(chunk_data_size >= (off_t)sizeof(buffer)); - if (mDataSource->read_at( + if (mDataSource->readAt( data_offset, buffer, 8) < 8) { return ERROR_IO; } @@ -492,7 +515,7 @@ status_t MPEG4Extractor::parseChunk(off_t *offset, int depth) { return ERROR_MALFORMED; } - if (mDataSource->read_at( + if (mDataSource->readAt( data_offset, buffer, sizeof(buffer)) < (ssize_t)sizeof(buffer)) { return ERROR_IO; } @@ -544,7 +567,7 @@ status_t MPEG4Extractor::parseChunk(off_t *offset, int depth) { return ERROR_MALFORMED; } - if (mDataSource->read_at( + if (mDataSource->readAt( data_offset, buffer, sizeof(buffer)) < (ssize_t)sizeof(buffer)) { return ERROR_IO; } @@ -655,7 +678,7 @@ status_t MPEG4Extractor::parseChunk(off_t *offset, int depth) { return ERROR_BUFFER_TOO_SMALL; } - if (mDataSource->read_at( + if (mDataSource->readAt( data_offset, buffer, chunk_data_size) < chunk_data_size) { return ERROR_IO; } @@ -679,7 +702,7 @@ status_t MPEG4Extractor::parseChunk(off_t *offset, int depth) { return ERROR_BUFFER_TOO_SMALL; } - if (mDataSource->read_at( + if (mDataSource->readAt( data_offset, buffer, chunk_data_size) < chunk_data_size) { return ERROR_IO; } @@ -722,7 +745,7 @@ sp<MediaSource> MPEG4Extractor::getTrack(size_t index) { } return new MPEG4Source( - track->meta, mDataSource, track->sampleTable); + track->meta, mDataSource, track->timescale, track->sampleTable); } //////////////////////////////////////////////////////////////////////////////// @@ -730,10 +753,11 @@ sp<MediaSource> MPEG4Extractor::getTrack(size_t index) { MPEG4Source::MPEG4Source( const sp<MetaData> &format, const sp<DataSource> &dataSource, + int32_t timeScale, const sp<SampleTable> &sampleTable) : mFormat(format), mDataSource(dataSource), - mTimescale(0), + mTimescale(timeScale), mSampleTable(sampleTable), mCurrentSampleIndex(0), mIsAVC(false), @@ -746,9 +770,6 @@ MPEG4Source::MPEG4Source( bool success = mFormat->findCString(kKeyMIMEType, &mime); CHECK(success); - success = mFormat->findInt32(kKeyTimeScale, &mTimescale); - CHECK(success); - mIsAVC = !strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_AVC); } @@ -868,7 +889,7 @@ status_t MPEG4Source::read( if (!mIsAVC || mWantsNALFragments) { if (newBuffer) { ssize_t num_bytes_read = - mDataSource->read_at(offset, (uint8_t *)mBuffer->data(), size); + mDataSource->readAt(offset, (uint8_t *)mBuffer->data(), size); if (num_bytes_read < (ssize_t)size) { mBuffer->release(); @@ -879,8 +900,8 @@ status_t MPEG4Source::read( mBuffer->set_range(0, size); mBuffer->meta_data()->clear(); - mBuffer->meta_data()->setInt32(kKeyTimeUnits, dts); - mBuffer->meta_data()->setInt32(kKeyTimeScale, mTimescale); + mBuffer->meta_data()->setInt64( + kKeyTime, ((int64_t)dts * 1000000) / mTimescale); ++mCurrentSampleIndex; } @@ -923,7 +944,7 @@ status_t MPEG4Source::read( // the start code (0x00 00 00 01). ssize_t num_bytes_read = - mDataSource->read_at(offset, mSrcBuffer, size); + mDataSource->readAt(offset, mSrcBuffer, size); if (num_bytes_read < (ssize_t)size) { mBuffer->release(); @@ -959,8 +980,8 @@ status_t MPEG4Source::read( mBuffer->set_range(0, dstOffset); mBuffer->meta_data()->clear(); - mBuffer->meta_data()->setInt32(kKeyTimeUnits, dts); - mBuffer->meta_data()->setInt32(kKeyTimeScale, mTimescale); + mBuffer->meta_data()->setInt64( + kKeyTime, ((int64_t)dts * 1000000) / mTimescale); ++mCurrentSampleIndex; *out = mBuffer; @@ -974,13 +995,14 @@ bool SniffMPEG4( const sp<DataSource> &source, String8 *mimeType, float *confidence) { uint8_t header[8]; - ssize_t n = source->read_at(4, header, sizeof(header)); + ssize_t n = source->readAt(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)) { + || !memcmp(header, "ftypisom", 8) || !memcmp(header, "ftypM4V ", 8) + || !memcmp(header, "ftypM4A ", 8)) { *mimeType = MEDIA_MIMETYPE_CONTAINER_MPEG4; *confidence = 0.1; diff --git a/media/libstagefright/MPEG4Writer.cpp b/media/libstagefright/MPEG4Writer.cpp index fa35768..367459f 100644 --- a/media/libstagefright/MPEG4Writer.cpp +++ b/media/libstagefright/MPEG4Writer.cpp @@ -75,6 +75,13 @@ MPEG4Writer::MPEG4Writer(const char *filename) CHECK(mFile != NULL); } +MPEG4Writer::MPEG4Writer(int fd) + : mFile(fdopen(fd, "wb")), + mOffset(0), + mMdatOffset(0) { + CHECK(mFile != NULL); +} + MPEG4Writer::~MPEG4Writer() { stop(); @@ -203,6 +210,27 @@ off_t MPEG4Writer::addSample(MediaBuffer *buffer) { return old_offset; } +off_t MPEG4Writer::addLengthPrefixedSample(MediaBuffer *buffer) { + Mutex::Autolock autoLock(mLock); + + off_t old_offset = mOffset; + + size_t length = buffer->range_length(); + CHECK(length < 65536); + + uint8_t x = length >> 8; + fwrite(&x, 1, 1, mFile); + x = length & 0xff; + fwrite(&x, 1, 1, mFile); + + fwrite((const uint8_t *)buffer->data() + buffer->range_offset(), + 1, length, mFile); + + mOffset += length + 2; + + return old_offset; +} + void MPEG4Writer::beginBox(const char *fourcc) { CHECK_EQ(strlen(fourcc), 4); @@ -348,11 +376,12 @@ void *MPEG4Writer::Track::ThreadWrapper(void *me) { } void MPEG4Writer::Track::threadEntry() { - bool is_mpeg4 = false; sp<MetaData> meta = mSource->getFormat(); const char *mime; meta->findCString(kKeyMIMEType, &mime); - is_mpeg4 = !strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_MPEG4); + bool is_mpeg4 = !strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_MPEG4); + bool is_avc = !strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_AVC); + int32_t count = 0; MediaBuffer *buffer; while (!mDone && mSource->read(&buffer) == OK) { @@ -363,6 +392,55 @@ void MPEG4Writer::Track::threadEntry() { continue; } + ++count; + + if (is_avc && count < 3) { + size_t size = buffer->range_length(); + + switch (count) { + case 1: + { + CHECK_EQ(mCodecSpecificData, NULL); + mCodecSpecificData = malloc(size + 8); + uint8_t *header = (uint8_t *)mCodecSpecificData; + header[0] = 1; + header[1] = 0x42; // profile + header[2] = 0x80; + header[3] = 0x1e; // level + header[4] = 0xfc | 3; + header[5] = 0xe0 | 1; + header[6] = size >> 8; + header[7] = size & 0xff; + memcpy(&header[8], + (const uint8_t *)buffer->data() + buffer->range_offset(), + size); + + mCodecSpecificDataSize = size + 8; + break; + } + + case 2: + { + size_t offset = mCodecSpecificDataSize; + mCodecSpecificDataSize += size + 3; + mCodecSpecificData = realloc(mCodecSpecificData, mCodecSpecificDataSize); + uint8_t *header = (uint8_t *)mCodecSpecificData; + header[offset] = 1; + header[offset + 1] = size >> 8; + header[offset + 2] = size & 0xff; + memcpy(&header[offset + 3], + (const uint8_t *)buffer->data() + buffer->range_offset(), + size); + break; + } + } + + buffer->release(); + buffer = NULL; + + continue; + } + if (mCodecSpecificData == NULL && is_mpeg4) { const uint8_t *data = (const uint8_t *)buffer->data() + buffer->range_offset(); @@ -393,21 +471,18 @@ void MPEG4Writer::Track::threadEntry() { buffer->set_range(buffer->range_offset() + offset, size - offset); } - off_t offset = mOwner->addSample(buffer); + off_t offset = is_avc ? mOwner->addLengthPrefixedSample(buffer) + : mOwner->addSample(buffer); SampleInfo info; - info.size = buffer->range_length(); + info.size = is_avc ? buffer->range_length() + 2 : buffer->range_length(); info.offset = offset; - int32_t units, scale; - bool success = - buffer->meta_data()->findInt32(kKeyTimeUnits, &units); - CHECK(success); - success = - buffer->meta_data()->findInt32(kKeyTimeScale, &scale); - CHECK(success); + int64_t timestampUs; + CHECK(buffer->meta_data()->findInt64(kKeyTime, ×tampUs)); - info.timestamp = (int64_t)units * 1000 / scale; + // Our timestamp is in ms. + info.timestamp = (timestampUs + 500) / 1000; mSampleInfos.push_back(info); @@ -560,6 +635,8 @@ void MPEG4Writer::Track::writeTrackHeader(int32_t trackID) { mOwner->beginBox("mp4v"); } else if (!strcasecmp(MEDIA_MIMETYPE_VIDEO_H263, mime)) { mOwner->beginBox("s263"); + } else if (!strcasecmp(MEDIA_MIMETYPE_VIDEO_AVC, mime)) { + mOwner->beginBox("avc1"); } else { LOGE("Unknown mime type '%s'.", mime); CHECK(!"should not be here, unknown mime type."); @@ -635,8 +712,13 @@ void MPEG4Writer::Track::writeTrackHeader(int32_t trackID) { mOwner->writeInt8(0); // profile: 0 mOwner->endBox(); // d263 + } else if (!strcasecmp(MEDIA_MIMETYPE_VIDEO_AVC, mime)) { + mOwner->beginBox("avcC"); + mOwner->write(mCodecSpecificData, mCodecSpecificDataSize); + mOwner->endBox(); // avcC } - mOwner->endBox(); // mp4v or s263 + + mOwner->endBox(); // mp4v, s263 or avc1 } mOwner->endBox(); // stsd diff --git a/media/libstagefright/MediaBuffer.cpp b/media/libstagefright/MediaBuffer.cpp index f3c0e73..b973745 100644 --- a/media/libstagefright/MediaBuffer.cpp +++ b/media/libstagefright/MediaBuffer.cpp @@ -108,10 +108,10 @@ size_t MediaBuffer::range_length() const { } void MediaBuffer::set_range(size_t offset, size_t length) { - if (offset < 0 || offset + length > mSize) { + if (offset + length > mSize) { LOGE("offset = %d, length = %d, mSize = %d", offset, length, mSize); } - CHECK(offset >= 0 && offset + length <= mSize); + CHECK(offset + length <= mSize); mRangeOffset = offset; mRangeLength = length; diff --git a/media/libstagefright/MediaDefs.cpp b/media/libstagefright/MediaDefs.cpp index 87b5b24..04b1454 100644 --- a/media/libstagefright/MediaDefs.cpp +++ b/media/libstagefright/MediaDefs.cpp @@ -32,5 +32,6 @@ const char *MEDIA_MIMETYPE_AUDIO_AAC = "audio/mp4a-latm"; const char *MEDIA_MIMETYPE_AUDIO_RAW = "audio/raw"; const char *MEDIA_MIMETYPE_CONTAINER_MPEG4 = "video/mpeg4"; +const char *MEDIA_MIMETYPE_CONTAINER_WAV = "audio/wav"; } // namespace android diff --git a/media/libstagefright/MediaExtractor.cpp b/media/libstagefright/MediaExtractor.cpp index 8535f52..19a1f85 100644 --- a/media/libstagefright/MediaExtractor.cpp +++ b/media/libstagefright/MediaExtractor.cpp @@ -18,12 +18,17 @@ #define LOG_TAG "MediaExtractor" #include <utils/Log.h> -#include <media/stagefright/AMRExtractor.h> +#include "include/AMRExtractor.h" +#include "include/MP3Extractor.h" +#include "include/MPEG4Extractor.h" +#include "include/WAVExtractor.h" + +#include <media/stagefright/CachingDataSource.h> #include <media/stagefright/DataSource.h> +#include <media/stagefright/HTTPDataSource.h> #include <media/stagefright/MediaDefs.h> -#include <media/stagefright/MP3Extractor.h> -#include <media/stagefright/MPEG4Extractor.h> #include <media/stagefright/MediaExtractor.h> +#include <media/stagefright/MmapSource.h> #include <utils/String8.h> namespace android { @@ -41,7 +46,7 @@ sp<MediaExtractor> MediaExtractor::Create( } mime = tmp.string(); - LOGI("Autodetected media content as '%s' with confidence %.2f", + LOGV("Autodetected media content as '%s' with confidence %.2f", mime, confidence); } @@ -53,9 +58,32 @@ sp<MediaExtractor> MediaExtractor::Create( } else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AMR_NB) || !strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AMR_WB)) { return new AMRExtractor(source); + } else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_WAV)) { + return new WAVExtractor(source); } return NULL; } +// static +sp<MediaExtractor> MediaExtractor::CreateFromURI( + const char *uri, const char *mime) { + sp<DataSource> source; + 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); + } + + if (source == NULL || source->initCheck() != OK) { + return NULL; + } + + return Create(source, mime); +} + } // namespace android diff --git a/media/libstagefright/MediaPlayerImpl.cpp b/media/libstagefright/MediaPlayerImpl.cpp index 8300422..c1044a3 100644 --- a/media/libstagefright/MediaPlayerImpl.cpp +++ b/media/libstagefright/MediaPlayerImpl.cpp @@ -18,16 +18,17 @@ #define LOG_TAG "MediaPlayerImpl" #include "utils/Log.h" +#include "include/stagefright_string.h" +#include "include/HTTPStream.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/MediaDebug.h> +#include <media/stagefright/MediaDefs.h> #include <media/stagefright/MediaExtractor.h> #include <media/stagefright/MediaPlayerImpl.h> #include <media/stagefright/MetaData.h> @@ -40,13 +41,6 @@ namespace android { -static void releaseBufferIfNonNULL(MediaBuffer **buffer) { - if (*buffer) { - (*buffer)->release(); - *buffer = NULL; - } -} - MediaPlayerImpl::MediaPlayerImpl(const char *uri) : mInitCheck(NO_INIT), mTimeSource(NULL), @@ -76,18 +70,7 @@ MediaPlayerImpl::MediaPlayerImpl(const char *uri) mVideoDecoder = CameraSource::Create(); #endif } else { - sp<DataSource> source; - 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); + mExtractor = MediaExtractor::CreateFromURI(uri); if (mExtractor == NULL) { return; @@ -236,8 +219,6 @@ void MediaPlayerImpl::videoEntry() { bool firstFrame = true; bool eof = false; - MediaBuffer *lastBuffer = NULL; - status_t err = mVideoDecoder->start(); CHECK_EQ(err, OK); @@ -250,8 +231,6 @@ void MediaPlayerImpl::videoEntry() { { Mutex::Autolock autoLock(mLock); if (mSeeking) { - releaseBufferIfNonNULL(&lastBuffer); - LOGV("seek-options to %lld", mSeekTimeUs); options.setSeekTo(mSeekTimeUs); @@ -269,6 +248,13 @@ void MediaPlayerImpl::videoEntry() { status_t err = mVideoDecoder->read(&buffer, &options); CHECK((err == OK && buffer != NULL) || (err != OK && buffer == NULL)); + if (err == INFO_FORMAT_CHANGED) { + LOGV("format changed."); + depopulateISurface(); + populateISurface(); + continue; + } + if (err == ERROR_END_OF_STREAM || err != OK) { eof = true; continue; @@ -280,15 +266,9 @@ void MediaPlayerImpl::videoEntry() { continue; } - int32_t units, scale; - bool success = - buffer->meta_data()->findInt32(kKeyTimeUnits, &units); - CHECK(success); - success = - buffer->meta_data()->findInt32(kKeyTimeScale, &scale); - CHECK(success); + int64_t pts_us; + CHECK(buffer->meta_data()->findInt64(kKeyTime, &pts_us)); - int64_t pts_us = (int64_t)units * 1000000 / scale; { Mutex::Autolock autoLock(mLock); mVideoPosition = pts_us; @@ -312,21 +292,19 @@ void MediaPlayerImpl::videoEntry() { firstFrame = false; } - displayOrDiscardFrame(&lastBuffer, buffer, pts_us); + displayOrDiscardFrame(buffer, pts_us); } - releaseBufferIfNonNULL(&lastBuffer); - mVideoDecoder->stop(); } void MediaPlayerImpl::displayOrDiscardFrame( - MediaBuffer **lastBuffer, MediaBuffer *buffer, int64_t pts_us) { for (;;) { if (!mPlaying || mPaused) { - releaseBufferIfNonNULL(lastBuffer); - *lastBuffer = buffer; + buffer->release(); + buffer = NULL; + return; } @@ -348,10 +326,12 @@ void MediaPlayerImpl::displayOrDiscardFrame( LOGV("we're late by %lld ms, dropping a frame\n", -delay_us / 1000); - releaseBufferIfNonNULL(lastBuffer); - *lastBuffer = buffer; + buffer->release(); + buffer = NULL; return; } else if (delay_us > 100000) { + LOGV("we're much too early (by %lld ms)\n", + delay_us / 1000); usleep(100000); continue; } else if (delay_us > 0) { @@ -363,14 +343,13 @@ void MediaPlayerImpl::displayOrDiscardFrame( { Mutex::Autolock autoLock(mLock); - if (mVideoRenderer.get() != NULL) { sendFrameToISurface(buffer); } } - releaseBufferIfNonNULL(lastBuffer); - *lastBuffer = buffer; + buffer->release(); + buffer = NULL; } void MediaPlayerImpl::init() { @@ -403,12 +382,10 @@ void MediaPlayerImpl::init() { sp<MediaSource> source = mExtractor->getTrack(i); - 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; + int64_t durationUs; + if (meta->findInt64(kKeyDuration, &durationUs)) { + if (durationUs > mDuration) { + mDuration = durationUs; } } @@ -427,8 +404,15 @@ void MediaPlayerImpl::setAudioSource(const sp<MediaSource> &source) { sp<MetaData> meta = source->getFormat(); - mAudioDecoder = OMXCodec::Create( - mClient.interface(), meta, false /* createEncoder */, source); + const char *mime; + CHECK(meta->findCString(kKeyMIMEType, &mime)); + + if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_RAW)) { + mAudioDecoder = source; + } else { + mAudioDecoder = OMXCodec::Create( + mClient.interface(), meta, false /* createEncoder */, source); + } } void MediaPlayerImpl::setVideoSource(const sp<MediaSource> &source) { @@ -633,6 +617,9 @@ void MediaPlayerImpl::populateISurface() { success = success && meta->findInt32(kKeyHeight, &decodedHeight); CHECK(success); + LOGV("mVideoWidth=%d, mVideoHeight=%d, decodedWidth=%d, decodedHeight=%d", + mVideoWidth, mVideoHeight, decodedWidth, decodedHeight); + if (mSurface.get() != NULL) { mVideoRenderer = mClient.interface()->createRenderer( diff --git a/media/libstagefright/MetaData.cpp b/media/libstagefright/MetaData.cpp index 6b067cb..63b476e 100644 --- a/media/libstagefright/MetaData.cpp +++ b/media/libstagefright/MetaData.cpp @@ -58,6 +58,10 @@ bool MetaData::setInt32(uint32_t key, int32_t value) { return setData(key, TYPE_INT32, &value, sizeof(value)); } +bool MetaData::setInt64(uint32_t key, int64_t value) { + return setData(key, TYPE_INT64, &value, sizeof(value)); +} + bool MetaData::setFloat(uint32_t key, float value) { return setData(key, TYPE_FLOAT, &value, sizeof(value)); } @@ -94,6 +98,21 @@ bool MetaData::findInt32(uint32_t key, int32_t *value) { return true; } +bool MetaData::findInt64(uint32_t key, int64_t *value) { + uint32_t type; + const void *data; + size_t size; + if (!findData(key, &type, &data, &size) || type != TYPE_INT64) { + return false; + } + + CHECK_EQ(size, sizeof(*value)); + + *value = *(int64_t *)data; + + return true; +} + bool MetaData::findFloat(uint32_t key, float *value) { uint32_t type; const void *data; diff --git a/media/libstagefright/MmapSource.cpp b/media/libstagefright/MmapSource.cpp index 47d95f9..42749cf 100644 --- a/media/libstagefright/MmapSource.cpp +++ b/media/libstagefright/MmapSource.cpp @@ -34,7 +34,10 @@ MmapSource::MmapSource(const char *filename) mBase(NULL), mSize(0) { LOGV("MmapSource '%s'", filename); - CHECK(mFd >= 0); + + if (mFd < 0) { + return; + } off_t size = lseek(mFd, 0, SEEK_END); mSize = (size_t)size; @@ -78,12 +81,12 @@ MmapSource::~MmapSource() { } } -status_t MmapSource::InitCheck() const { +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); +ssize_t MmapSource::readAt(off_t offset, void *data, size_t size) { + LOGV("readAt offset:%ld data:%p size:%d", offset, data, size); CHECK(offset >= 0); size_t avail = 0; diff --git a/media/libstagefright/OMXCodec.cpp b/media/libstagefright/OMXCodec.cpp index ebf1e0c..d36653e 100644 --- a/media/libstagefright/OMXCodec.cpp +++ b/media/libstagefright/OMXCodec.cpp @@ -18,11 +18,12 @@ #define LOG_TAG "OMXCodec" #include <utils/Log.h> +#include "include/ESDS.h" + #include <binder/IServiceManager.h> #include <binder/MemoryDealer.h> #include <binder/ProcessState.h> #include <media/IMediaPlayerService.h> -#include <media/stagefright/ESDS.h> #include <media/stagefright/MediaBuffer.h> #include <media/stagefright/MediaBufferGroup.h> #include <media/stagefright/MediaDebug.h> @@ -39,6 +40,8 @@ namespace android { +static const int OMX_QCOM_COLOR_FormatYVU420SemiPlanar = 0x7FA30C00; + struct CodecInfo { const char *mime; const char *codec; @@ -169,52 +172,37 @@ static void InitOMXParams(T *params) { params->nVersion.s.nStep = 0; } -// static -sp<OMXCodec> OMXCodec::Create( - const sp<IOMX> &omx, - const sp<MetaData> &meta, bool createEncoder, - const sp<MediaSource> &source, - const char *matchComponentName) { - const char *mime; - bool success = meta->findCString(kKeyMIMEType, &mime); - CHECK(success); +static bool IsSoftwareCodec(const char *componentName) { + if (!strncmp("OMX.PV.", componentName, 7)) { + return true; + } - const char *componentName = NULL; - sp<OMXCodecObserver> observer = new OMXCodecObserver; - IOMX::node_id node = 0; - for (int index = 0;; ++index) { - if (createEncoder) { - componentName = GetCodec( - kEncoderInfo, sizeof(kEncoderInfo) / sizeof(kEncoderInfo[0]), - mime, index); - } else { - componentName = GetCodec( - kDecoderInfo, sizeof(kDecoderInfo) / sizeof(kDecoderInfo[0]), - mime, index); - } + return false; +} - if (!componentName) { - return NULL; - } +static int CompareSoftwareCodecsFirst( + const String8 *elem1, const String8 *elem2) { + bool isSoftwareCodec1 = IsSoftwareCodec(elem1->string()); + bool isSoftwareCodec2 = IsSoftwareCodec(elem2->string()); - // If a specific codec is requested, skip the non-matching ones. - if (matchComponentName && strcmp(componentName, matchComponentName)) { - continue; - } - - LOGV("Attempting to allocate OMX node '%s'", componentName); + if (isSoftwareCodec1) { + if (isSoftwareCodec2) { return 0; } + return -1; + } - status_t err = omx->allocateNode(componentName, observer, &node); - if (err == OK) { - LOGI("Successfully allocated OMX node '%s'", componentName); - break; - } + if (isSoftwareCodec2) { + return 1; } + return 0; +} + +// static +uint32_t OMXCodec::getComponentQuirks(const char *componentName) { uint32_t quirks = 0; + if (!strcmp(componentName, "OMX.PV.avcdec")) { quirks |= kWantsNALFragments; - quirks |= kOutputDimensionsAre16Aligned; } if (!strcmp(componentName, "OMX.TI.MP3.decode")) { quirks |= kNeedsFlushBeforeDisable; @@ -222,20 +210,15 @@ sp<OMXCodec> OMXCodec::Create( if (!strcmp(componentName, "OMX.TI.AAC.decode")) { quirks |= kNeedsFlushBeforeDisable; quirks |= kRequiresFlushCompleteEmulation; - - // The following is currently necessary for proper shutdown - // behaviour, but NOT enabled by default in order to make the - // bug reproducible... - // quirks |= kRequiresFlushBeforeShutdown; } if (!strncmp(componentName, "OMX.qcom.video.encoder.", 23)) { quirks |= kRequiresLoadedToIdleAfterAllocation; quirks |= kRequiresAllocateBufferOnInputPorts; + quirks |= kRequiresAllocateBufferOnOutputPorts; } if (!strncmp(componentName, "OMX.qcom.video.decoder.", 23)) { // XXX Required on P....on only. quirks |= kRequiresAllocateBufferOnOutputPorts; - quirks |= kOutputDimensionsAre16Aligned; } if (!strncmp(componentName, "OMX.TI.", 7)) { @@ -248,8 +231,94 @@ sp<OMXCodec> OMXCodec::Create( quirks |= kRequiresAllocateBufferOnOutputPorts; } + return quirks; +} + +// static +void OMXCodec::findMatchingCodecs( + const char *mime, + bool createEncoder, const char *matchComponentName, + uint32_t flags, + Vector<String8> *matchingCodecs) { + matchingCodecs->clear(); + + for (int index = 0;; ++index) { + const char *componentName; + + if (createEncoder) { + componentName = GetCodec( + kEncoderInfo, + sizeof(kEncoderInfo) / sizeof(kEncoderInfo[0]), + mime, index); + } else { + componentName = GetCodec( + kDecoderInfo, + sizeof(kDecoderInfo) / sizeof(kDecoderInfo[0]), + mime, index); + } + + if (!componentName) { + break; + } + + // If a specific codec is requested, skip the non-matching ones. + if (matchComponentName && strcmp(componentName, matchComponentName)) { + continue; + } + + matchingCodecs->push(String8(componentName)); + } + + if (flags & kPreferSoftwareCodecs) { + matchingCodecs->sort(CompareSoftwareCodecsFirst); + } +} + +// static +sp<OMXCodec> OMXCodec::Create( + const sp<IOMX> &omx, + const sp<MetaData> &meta, bool createEncoder, + const sp<MediaSource> &source, + const char *matchComponentName, + uint32_t flags) { + const char *mime; + bool success = meta->findCString(kKeyMIMEType, &mime); + CHECK(success); + + Vector<String8> matchingCodecs; + findMatchingCodecs( + mime, createEncoder, matchComponentName, flags, &matchingCodecs); + + if (matchingCodecs.isEmpty()) { + return NULL; + } + + sp<OMXCodecObserver> observer = new OMXCodecObserver; + IOMX::node_id node = 0; + success = false; + + const char *componentName; + for (size_t i = 0; i < matchingCodecs.size(); ++i) { + componentName = matchingCodecs[i].string(); + + LOGV("Attempting to allocate OMX node '%s'", componentName); + + status_t err = omx->allocateNode(componentName, observer, &node); + if (err == OK) { + LOGV("Successfully allocated OMX node '%s'", componentName); + + success = true; + break; + } + } + + if (!success) { + return NULL; + } + sp<OMXCodec> codec = new OMXCodec( - omx, node, quirks, createEncoder, mime, componentName, + omx, node, getComponentQuirks(componentName), + createEncoder, mime, componentName, source); observer->setCodec(codec); @@ -331,7 +400,7 @@ sp<OMXCodec> OMXCodec::Create( size -= length; } - LOGI("AVC profile = %d (%s), level = %d", + LOGV("AVC profile = %d (%s), level = %d", (int)profile, AVCProfileToString(profile), (int)level / 10); if (!strcmp(componentName, "OMX.TI.Video.Decoder") @@ -452,7 +521,7 @@ status_t OMXCodec::setVideoPortFormatType( // CHECK_EQ(format.nIndex, index); #if 1 - CODEC_LOGI("portIndex: %ld, index: %ld, eCompressionFormat=%d eColorFormat=%d", + CODEC_LOGV("portIndex: %ld, index: %ld, eCompressionFormat=%d eColorFormat=%d", portIndex, index, format.eCompressionFormat, format.eColorFormat); #endif @@ -493,9 +562,25 @@ status_t OMXCodec::setVideoPortFormatType( return err; } +static size_t getFrameSize( + OMX_COLOR_FORMATTYPE colorFormat, int32_t width, int32_t height) { + switch (colorFormat) { + case OMX_COLOR_FormatYCbYCr: + case OMX_COLOR_FormatCbYCrY: + return width * height * 2; + + case OMX_COLOR_FormatYUV420SemiPlanar: + return (width * height * 3) / 2; + + default: + CHECK(!"Should not be here. Unsupported color format."); + break; + } +} + void OMXCodec::setVideoInputFormat( const char *mime, OMX_U32 width, OMX_U32 height) { - CODEC_LOGI("setVideoInputFormat width=%ld, height=%ld", width, height); + CODEC_LOGV("setVideoInputFormat width=%ld, height=%ld", width, height); OMX_VIDEO_CODINGTYPE compressionFormat = OMX_VIDEO_CodingUnused; if (!strcasecmp(MEDIA_MIMETYPE_VIDEO_AVC, mime)) { @@ -509,19 +594,18 @@ void OMXCodec::setVideoInputFormat( CHECK(!"Should not be here. Not a supported video mime type."); } - OMX_COLOR_FORMATTYPE colorFormat = - 0 ? OMX_COLOR_FormatYCbYCr : OMX_COLOR_FormatCbYCrY; - - if (!strncmp("OMX.qcom.video.encoder.", mComponentName, 23)) { - colorFormat = OMX_COLOR_FormatYUV420SemiPlanar; + OMX_COLOR_FORMATTYPE colorFormat = OMX_COLOR_FormatYUV420SemiPlanar; + if (!strcasecmp("OMX.TI.Video.encoder", mComponentName)) { + colorFormat = OMX_COLOR_FormatYCbYCr; } - setVideoPortFormatType( + CHECK_EQ(setVideoPortFormatType( kPortIndexInput, OMX_VIDEO_CodingUnused, - colorFormat); + colorFormat), OK); - setVideoPortFormatType( - kPortIndexOutput, compressionFormat, OMX_COLOR_FormatUnused); + CHECK_EQ(setVideoPortFormatType( + kPortIndexOutput, compressionFormat, OMX_COLOR_FormatUnused), + OK); OMX_PARAM_PORTDEFINITIONTYPE def; InitOMXParams(&def); @@ -554,8 +638,8 @@ void OMXCodec::setVideoInputFormat( mNode, OMX_IndexParamPortDefinition, &def, sizeof(def)); CHECK_EQ(err, OK); - def.nBufferSize = (width * height * 2); // (width * height * 3) / 2; - CODEC_LOGI("Setting nBufferSize = %ld", def.nBufferSize); + def.nBufferSize = getFrameSize(colorFormat, width, height); + CODEC_LOGV("Setting nBufferSize = %ld", def.nBufferSize); CHECK_EQ(def.eDomain, OMX_PortDomainVideo); @@ -564,14 +648,172 @@ void OMXCodec::setVideoInputFormat( video_def->eCompressionFormat = OMX_VIDEO_CodingUnused; video_def->eColorFormat = colorFormat; + video_def->xFramerate = 24 << 16; // XXX crucial! + err = mOMX->setParameter( mNode, OMX_IndexParamPortDefinition, &def, sizeof(def)); CHECK_EQ(err, OK); + + switch (compressionFormat) { + case OMX_VIDEO_CodingMPEG4: + { + CHECK_EQ(setupMPEG4EncoderParameters(), OK); + break; + } + + case OMX_VIDEO_CodingH263: + break; + + case OMX_VIDEO_CodingAVC: + { + CHECK_EQ(setupAVCEncoderParameters(), OK); + break; + } + + default: + CHECK(!"Support for this compressionFormat to be implemented."); + break; + } +} + +status_t OMXCodec::setupMPEG4EncoderParameters() { + OMX_VIDEO_PARAM_MPEG4TYPE mpeg4type; + InitOMXParams(&mpeg4type); + mpeg4type.nPortIndex = kPortIndexOutput; + + status_t err = mOMX->getParameter( + mNode, OMX_IndexParamVideoMpeg4, &mpeg4type, sizeof(mpeg4type)); + CHECK_EQ(err, OK); + + mpeg4type.nSliceHeaderSpacing = 0; + mpeg4type.bSVH = OMX_FALSE; + mpeg4type.bGov = OMX_FALSE; + + mpeg4type.nAllowedPictureTypes = + OMX_VIDEO_PictureTypeI | OMX_VIDEO_PictureTypeP; + + mpeg4type.nPFrames = 23; + mpeg4type.nBFrames = 0; + + mpeg4type.nIDCVLCThreshold = 0; + mpeg4type.bACPred = OMX_TRUE; + mpeg4type.nMaxPacketSize = 256; + mpeg4type.nTimeIncRes = 1000; + mpeg4type.nHeaderExtension = 0; + mpeg4type.bReversibleVLC = OMX_FALSE; + + mpeg4type.eProfile = OMX_VIDEO_MPEG4ProfileCore; + mpeg4type.eLevel = OMX_VIDEO_MPEG4Level2; + + err = mOMX->setParameter( + mNode, OMX_IndexParamVideoMpeg4, &mpeg4type, sizeof(mpeg4type)); + CHECK_EQ(err, OK); + + // ---------------- + + OMX_VIDEO_PARAM_BITRATETYPE bitrateType; + InitOMXParams(&bitrateType); + bitrateType.nPortIndex = kPortIndexOutput; + + err = mOMX->getParameter( + mNode, OMX_IndexParamVideoBitrate, + &bitrateType, sizeof(bitrateType)); + CHECK_EQ(err, OK); + + bitrateType.eControlRate = OMX_Video_ControlRateVariable; + bitrateType.nTargetBitrate = 1000000; + + err = mOMX->setParameter( + mNode, OMX_IndexParamVideoBitrate, + &bitrateType, sizeof(bitrateType)); + CHECK_EQ(err, OK); + + // ---------------- + + OMX_VIDEO_PARAM_ERRORCORRECTIONTYPE errorCorrectionType; + InitOMXParams(&errorCorrectionType); + errorCorrectionType.nPortIndex = kPortIndexOutput; + + err = mOMX->getParameter( + mNode, OMX_IndexParamVideoErrorCorrection, + &errorCorrectionType, sizeof(errorCorrectionType)); + CHECK_EQ(err, OK); + + errorCorrectionType.bEnableHEC = OMX_FALSE; + errorCorrectionType.bEnableResync = OMX_TRUE; + errorCorrectionType.nResynchMarkerSpacing = 256; + errorCorrectionType.bEnableDataPartitioning = OMX_FALSE; + errorCorrectionType.bEnableRVLC = OMX_FALSE; + + err = mOMX->setParameter( + mNode, OMX_IndexParamVideoErrorCorrection, + &errorCorrectionType, sizeof(errorCorrectionType)); + CHECK_EQ(err, OK); + + return OK; +} + +status_t OMXCodec::setupAVCEncoderParameters() { + OMX_VIDEO_PARAM_AVCTYPE h264type; + InitOMXParams(&h264type); + h264type.nPortIndex = kPortIndexOutput; + + status_t err = mOMX->getParameter( + mNode, OMX_IndexParamVideoAvc, &h264type, sizeof(h264type)); + CHECK_EQ(err, OK); + + h264type.nAllowedPictureTypes = + OMX_VIDEO_PictureTypeI | OMX_VIDEO_PictureTypeP; + + h264type.nSliceHeaderSpacing = 0; + h264type.nBFrames = 0; + h264type.bUseHadamard = OMX_TRUE; + h264type.nRefFrames = 1; + h264type.nRefIdx10ActiveMinus1 = 0; + h264type.nRefIdx11ActiveMinus1 = 0; + h264type.bEnableUEP = OMX_FALSE; + h264type.bEnableFMO = OMX_FALSE; + h264type.bEnableASO = OMX_FALSE; + h264type.bEnableRS = OMX_FALSE; + h264type.eProfile = OMX_VIDEO_AVCProfileBaseline; + h264type.eLevel = OMX_VIDEO_AVCLevel1b; + h264type.bFrameMBsOnly = OMX_TRUE; + h264type.bMBAFF = OMX_FALSE; + h264type.bEntropyCodingCABAC = OMX_FALSE; + h264type.bWeightedPPrediction = OMX_FALSE; + h264type.bconstIpred = OMX_FALSE; + h264type.bDirect8x8Inference = OMX_FALSE; + h264type.bDirectSpatialTemporal = OMX_FALSE; + h264type.nCabacInitIdc = 0; + h264type.eLoopFilterMode = OMX_VIDEO_AVCLoopFilterEnable; + + err = mOMX->setParameter( + mNode, OMX_IndexParamVideoAvc, &h264type, sizeof(h264type)); + CHECK_EQ(err, OK); + + OMX_VIDEO_PARAM_BITRATETYPE bitrateType; + InitOMXParams(&bitrateType); + bitrateType.nPortIndex = kPortIndexOutput; + + err = mOMX->getParameter( + mNode, OMX_IndexParamVideoBitrate, + &bitrateType, sizeof(bitrateType)); + CHECK_EQ(err, OK); + + bitrateType.eControlRate = OMX_Video_ControlRateVariable; + bitrateType.nTargetBitrate = 1000000; + + err = mOMX->setParameter( + mNode, OMX_IndexParamVideoBitrate, + &bitrateType, sizeof(bitrateType)); + CHECK_EQ(err, OK); + + return OK; } void OMXCodec::setVideoOutputFormat( const char *mime, OMX_U32 width, OMX_U32 height) { - CODEC_LOGI("setVideoOutputFormat width=%ld, height=%ld", width, height); + CODEC_LOGV("setVideoOutputFormat width=%ld, height=%ld", width, height); OMX_VIDEO_CODINGTYPE compressionFormat = OMX_VIDEO_CodingUnused; if (!strcasecmp(MEDIA_MIMETYPE_VIDEO_AVC, mime)) { @@ -639,6 +881,7 @@ void OMXCodec::setVideoOutputFormat( video_def->nFrameWidth = width; video_def->nFrameHeight = height; + video_def->eCompressionFormat = compressionFormat; video_def->eColorFormat = OMX_COLOR_FormatUnused; err = mOMX->setParameter( @@ -668,7 +911,6 @@ void OMXCodec::setVideoOutputFormat( CHECK_EQ(err, OK); } - OMXCodec::OMXCodec( const sp<IOMX> &omx, IOMX::node_id node, uint32_t quirks, bool isEncoder, @@ -687,6 +929,7 @@ OMXCodec::OMXCodec( mInitialBufferSubmit(true), mSignalledEOS(false), mNoMoreOutputData(false), + mOutputPortSettingsHaveChanged(false), mSeekTimeUs(-1) { mPortStatus[kPortIndexInput] = ENABLED; mPortStatus[kPortIndexOutput] = ENABLED; @@ -990,16 +1233,15 @@ void OMXCodec::on_message(const omx_message &msg) { buffer->meta_data()->clear(); - buffer->meta_data()->setInt32( - kKeyTimeUnits, - (msg.u.extended_buffer_data.timestamp + 500) / 1000); - - buffer->meta_data()->setInt32( - kKeyTimeScale, 1000); + buffer->meta_data()->setInt64( + kKeyTime, msg.u.extended_buffer_data.timestamp); if (msg.u.extended_buffer_data.flags & OMX_BUFFERFLAG_SYNCFRAME) { buffer->meta_data()->setInt32(kKeyIsSyncFrame, true); } + if (msg.u.extended_buffer_data.flags & OMX_BUFFERFLAG_CODECCONFIG) { + buffer->meta_data()->setInt32(kKeyIsCodecConfig, true); + } buffer->meta_data()->setPointer( kKeyPlatformPrivate, @@ -1064,6 +1306,71 @@ void OMXCodec::onEvent(OMX_EVENTTYPE event, OMX_U32 data1, OMX_U32 data2) { } } +// Has the format changed in any way that the client would have to be aware of? +static bool formatHasNotablyChanged( + const sp<MetaData> &from, const sp<MetaData> &to) { + if (from.get() == NULL && to.get() == NULL) { + return false; + } + + if ((from.get() == NULL && to.get() != NULL) + || (from.get() != NULL && to.get() == NULL)) { + return true; + } + + const char *mime_from, *mime_to; + CHECK(from->findCString(kKeyMIMEType, &mime_from)); + CHECK(to->findCString(kKeyMIMEType, &mime_to)); + + if (strcasecmp(mime_from, mime_to)) { + return true; + } + + if (!strcasecmp(mime_from, MEDIA_MIMETYPE_VIDEO_RAW)) { + int32_t colorFormat_from, colorFormat_to; + CHECK(from->findInt32(kKeyColorFormat, &colorFormat_from)); + CHECK(to->findInt32(kKeyColorFormat, &colorFormat_to)); + + if (colorFormat_from != colorFormat_to) { + return true; + } + + int32_t width_from, width_to; + CHECK(from->findInt32(kKeyWidth, &width_from)); + CHECK(to->findInt32(kKeyWidth, &width_to)); + + if (width_from != width_to) { + return true; + } + + int32_t height_from, height_to; + CHECK(from->findInt32(kKeyHeight, &height_from)); + CHECK(to->findInt32(kKeyHeight, &height_to)); + + if (height_from != height_to) { + return true; + } + } else if (!strcasecmp(mime_from, MEDIA_MIMETYPE_AUDIO_RAW)) { + int32_t numChannels_from, numChannels_to; + CHECK(from->findInt32(kKeyChannelCount, &numChannels_from)); + CHECK(to->findInt32(kKeyChannelCount, &numChannels_to)); + + if (numChannels_from != numChannels_to) { + return true; + } + + int32_t sampleRate_from, sampleRate_to; + CHECK(from->findInt32(kKeySampleRate, &sampleRate_from)); + CHECK(to->findInt32(kKeySampleRate, &sampleRate_to)); + + if (sampleRate_from != sampleRate_to) { + return true; + } + } + + return false; +} + void OMXCodec::onCmdComplete(OMX_COMMANDTYPE cmd, OMX_U32 data) { switch (cmd) { case OMX_CommandStateSet: @@ -1086,6 +1393,15 @@ void OMXCodec::onCmdComplete(OMX_COMMANDTYPE cmd, OMX_U32 data) { if (mState == RECONFIGURING) { CHECK_EQ(portIndex, kPortIndexOutput); + sp<MetaData> oldOutputFormat = mOutputFormat; + initOutputFormat(mSource->getFormat()); + + // Don't notify clients if the output port settings change + // wasn't of importance to them, i.e. it may be that just the + // number of buffers has changed and nothing else. + mOutputPortSettingsHaveChanged = + formatHasNotablyChanged(oldOutputFormat, mOutputFormat); + enablePortAsync(portIndex); status_t err = allocateBuffersOnPort(portIndex); @@ -1443,7 +1759,7 @@ void OMXCodec::drainInputBuffer(BufferInfo *info) { } OMX_U32 flags = OMX_BUFFERFLAG_ENDOFFRAME; - OMX_TICKS timestamp = 0; + OMX_TICKS timestampUs = 0; size_t srcLength = 0; if (err != OK) { @@ -1463,15 +1779,11 @@ void OMXCodec::drainInputBuffer(BufferInfo *info) { (const uint8_t *)srcBuffer->data() + srcBuffer->range_offset(), srcLength); - int32_t units, scale; - if (srcBuffer->meta_data()->findInt32(kKeyTimeUnits, &units) - && srcBuffer->meta_data()->findInt32(kKeyTimeScale, &scale)) { - timestamp = ((OMX_TICKS)units * 1000000) / scale; - - CODEC_LOGV("Calling empty_buffer on buffer %p (length %d)", + if (srcBuffer->meta_data()->findInt64(kKeyTime, ×tampUs)) { + CODEC_LOGV("Calling emptyBuffer on buffer %p (length %d)", info->mBuffer, srcLength); - CODEC_LOGV("Calling empty_buffer with timestamp %lld us (%.2f secs)", - timestamp, timestamp / 1E6); + CODEC_LOGV("Calling emptyBuffer with timestamp %lld us (%.2f secs)", + timestampUs, timestampUs / 1E6); } } @@ -1482,7 +1794,7 @@ void OMXCodec::drainInputBuffer(BufferInfo *info) { err = mOMX->emptyBuffer( mNode, info->mBuffer, 0, srcLength, - flags, timestamp); + flags, timestampUs); if (err != OK) { setState(ERROR); @@ -1490,6 +1802,13 @@ void OMXCodec::drainInputBuffer(BufferInfo *info) { } info->mOwnedByComponent = true; + + // This component does not ever signal the EOS flag on output buffers, + // Thanks for nothing. + if (mSignalledEOS && !strcmp(mComponentName, "OMX.TI.Video.encoder")) { + mNoMoreOutputData = true; + mBufferFilled.signal(); + } } void OMXCodec::fillOutputBuffer(BufferInfo *info) { @@ -1794,6 +2113,7 @@ status_t OMXCodec::start(MetaData *) { mInitialBufferSubmit = true; mSignalledEOS = false; mNoMoreOutputData = false; + mOutputPortSettingsHaveChanged = false; mSeekTimeUs = -1; mFilledBuffers.clear(); @@ -1864,6 +2184,8 @@ status_t OMXCodec::stop() { } sp<MetaData> OMXCodec::getFormat() { + Mutex::Autolock autoLock(mLock); + return mOutputFormat; } @@ -1941,6 +2263,12 @@ status_t OMXCodec::read( return ERROR_END_OF_STREAM; } + if (mOutputPortSettingsHaveChanged) { + mOutputPortSettingsHaveChanged = false; + + return INFO_FORMAT_CHANGED; + } + size_t index = *mFilledBuffers.begin(); mFilledBuffers.erase(mFilledBuffers.begin()); @@ -2041,8 +2369,6 @@ static const char *colorFormatString(OMX_COLOR_FORMATTYPE type) { size_t numNames = sizeof(kNames) / sizeof(kNames[0]); - static const int OMX_QCOM_COLOR_FormatYVU420SemiPlanar = 0x7FA30C00; - if (type == OMX_QCOM_COLOR_FormatYVU420SemiPlanar) { return "OMX_QCOM_COLOR_FormatYVU420SemiPlanar"; } else if (type < 0 || (size_t)type >= numNames) { @@ -2410,7 +2736,7 @@ void OMXCodec::initOutputFormat(const sp<MetaData> &inputFormat) { CHECK(!"Unknown compression format."); } - if (mQuirks & kOutputDimensionsAre16Aligned) { + if (!strcmp(mComponentName, "OMX.PV.avcdec")) { // This component appears to be lying to me. mOutputFormat->setInt32( kKeyWidth, (video_def->nFrameWidth + 15) & -16); diff --git a/media/libstagefright/SampleTable.cpp b/media/libstagefright/SampleTable.cpp index 8efa7c7..4aec0e9 100644 --- a/media/libstagefright/SampleTable.cpp +++ b/media/libstagefright/SampleTable.cpp @@ -17,11 +17,12 @@ #define LOG_TAG "SampleTable" #include <utils/Log.h> +#include "include/SampleTable.h" + #include <arpa/inet.h> #include <media/stagefright/DataSource.h> #include <media/stagefright/MediaDebug.h> -#include <media/stagefright/SampleTable.h> #include <media/stagefright/Utils.h> namespace android { @@ -54,7 +55,7 @@ SampleTable::~SampleTable() { } status_t SampleTable::setChunkOffsetParams( - uint32_t type, off_t data_offset, off_t data_size) { + uint32_t type, off_t data_offset, size_t data_size) { if (mChunkOffsetOffset >= 0) { return ERROR_MALFORMED; } @@ -69,7 +70,7 @@ status_t SampleTable::setChunkOffsetParams( } uint8_t header[8]; - if (mDataSource->read_at( + if (mDataSource->readAt( data_offset, header, sizeof(header)) < (ssize_t)sizeof(header)) { return ERROR_IO; } @@ -95,7 +96,7 @@ status_t SampleTable::setChunkOffsetParams( } status_t SampleTable::setSampleToChunkParams( - off_t data_offset, off_t data_size) { + off_t data_offset, size_t data_size) { if (mSampleToChunkOffset >= 0) { return ERROR_MALFORMED; } @@ -107,7 +108,7 @@ status_t SampleTable::setSampleToChunkParams( } uint8_t header[8]; - if (mDataSource->read_at( + if (mDataSource->readAt( data_offset, header, sizeof(header)) < (ssize_t)sizeof(header)) { return ERROR_IO; } @@ -127,7 +128,7 @@ status_t SampleTable::setSampleToChunkParams( } status_t SampleTable::setSampleSizeParams( - uint32_t type, off_t data_offset, off_t data_size) { + uint32_t type, off_t data_offset, size_t data_size) { if (mSampleSizeOffset >= 0) { return ERROR_MALFORMED; } @@ -141,7 +142,7 @@ status_t SampleTable::setSampleSizeParams( } uint8_t header[12]; - if (mDataSource->read_at( + if (mDataSource->readAt( data_offset, header, sizeof(header)) < (ssize_t)sizeof(header)) { return ERROR_IO; } @@ -187,13 +188,13 @@ status_t SampleTable::setSampleSizeParams( } status_t SampleTable::setTimeToSampleParams( - off_t data_offset, off_t data_size) { + off_t data_offset, size_t data_size) { if (mTimeToSample != NULL || data_size < 8) { return ERROR_MALFORMED; } uint8_t header[8]; - if (mDataSource->read_at( + if (mDataSource->readAt( data_offset, header, sizeof(header)) < (ssize_t)sizeof(header)) { return ERROR_IO; } @@ -207,7 +208,7 @@ status_t SampleTable::setTimeToSampleParams( mTimeToSample = new uint32_t[mTimeToSampleCount * 2]; size_t size = sizeof(uint32_t) * mTimeToSampleCount * 2; - if (mDataSource->read_at( + if (mDataSource->readAt( data_offset + 8, mTimeToSample, size) < (ssize_t)size) { return ERROR_IO; } @@ -219,7 +220,7 @@ status_t SampleTable::setTimeToSampleParams( return OK; } -status_t SampleTable::setSyncSampleParams(off_t data_offset, off_t data_size) { +status_t SampleTable::setSyncSampleParams(off_t data_offset, size_t data_size) { if (mSyncSampleOffset >= 0 || data_size < 8) { return ERROR_MALFORMED; } @@ -227,7 +228,7 @@ status_t SampleTable::setSyncSampleParams(off_t data_offset, off_t data_size) { mSyncSampleOffset = data_offset; uint8_t header[8]; - if (mDataSource->read_at( + if (mDataSource->readAt( data_offset, header, sizeof(header)) < (ssize_t)sizeof(header)) { return ERROR_IO; } @@ -263,7 +264,7 @@ status_t SampleTable::getChunkOffset(uint32_t chunk_index, off_t *offset) { if (mChunkOffsetType == kChunkOffsetType32) { uint32_t offset32; - if (mDataSource->read_at( + if (mDataSource->readAt( mChunkOffsetOffset + 8 + 4 * chunk_index, &offset32, sizeof(offset32)) < (ssize_t)sizeof(offset32)) { @@ -275,7 +276,7 @@ status_t SampleTable::getChunkOffset(uint32_t chunk_index, off_t *offset) { CHECK_EQ(mChunkOffsetType, kChunkOffsetType64); uint64_t offset64; - if (mDataSource->read_at( + if (mDataSource->readAt( mChunkOffsetOffset + 8 + 8 * chunk_index, &offset64, sizeof(offset64)) < (ssize_t)sizeof(offset64)) { @@ -312,7 +313,7 @@ status_t SampleTable::getChunkForSample( uint32_t index = 0; while (index < mNumSampleToChunkOffsets) { uint8_t buffer[12]; - if (mDataSource->read_at(mSampleToChunkOffset + 8 + index * 12, + if (mDataSource->readAt(mSampleToChunkOffset + 8 + index * 12, buffer, sizeof(buffer)) < (ssize_t)sizeof(buffer)) { return ERROR_IO; } @@ -361,7 +362,7 @@ status_t SampleTable::getSampleSize( switch (mSampleSizeFieldSize) { case 32: { - if (mDataSource->read_at( + if (mDataSource->readAt( mSampleSizeOffset + 12 + 4 * sample_index, sample_size, sizeof(*sample_size)) < (ssize_t)sizeof(*sample_size)) { return ERROR_IO; @@ -374,7 +375,7 @@ status_t SampleTable::getSampleSize( case 16: { uint16_t x; - if (mDataSource->read_at( + if (mDataSource->readAt( mSampleSizeOffset + 12 + 2 * sample_index, &x, sizeof(x)) < (ssize_t)sizeof(x)) { return ERROR_IO; @@ -387,7 +388,7 @@ status_t SampleTable::getSampleSize( case 8: { uint8_t x; - if (mDataSource->read_at( + if (mDataSource->readAt( mSampleSizeOffset + 12 + sample_index, &x, sizeof(x)) < (ssize_t)sizeof(x)) { return ERROR_IO; @@ -402,7 +403,7 @@ status_t SampleTable::getSampleSize( CHECK_EQ(mSampleSizeFieldSize, 4); uint8_t x; - if (mDataSource->read_at( + if (mDataSource->readAt( mSampleSizeOffset + 12 + sample_index / 2, &x, sizeof(x)) < (ssize_t)sizeof(x)) { return ERROR_IO; @@ -553,7 +554,7 @@ status_t SampleTable::findClosestSyncSample( uint32_t right = mNumSyncSamples; while (left < right) { uint32_t mid = (left + right) / 2; - if (mDataSource->read_at( + if (mDataSource->readAt( mSyncSampleOffset + 8 + (mid - 1) * 4, &x, 4) != 4) { return ERROR_IO; } @@ -574,5 +575,52 @@ status_t SampleTable::findClosestSyncSample( return OK; } +status_t SampleTable::findThumbnailSample(uint32_t *sample_index) { + if (mSyncSampleOffset < 0) { + // All samples are sync-samples. + *sample_index = 0; + return OK; + } + + uint32_t bestSampleIndex = 0; + size_t maxSampleSize = 0; + + static const size_t kMaxNumSyncSamplesToScan = 20; + + // Consider the first kMaxNumSyncSamplesToScan sync samples and + // pick the one with the largest (compressed) size as the thumbnail. + + size_t numSamplesToScan = mNumSyncSamples; + if (numSamplesToScan > kMaxNumSyncSamplesToScan) { + numSamplesToScan = kMaxNumSyncSamplesToScan; + } + + for (size_t i = 0; i < numSamplesToScan; ++i) { + uint32_t x; + if (mDataSource->readAt( + mSyncSampleOffset + 8 + i * 4, &x, 4) != 4) { + return ERROR_IO; + } + x = ntohl(x); + --x; + + // Now x is a sample index. + size_t sampleSize; + status_t err = getSampleSize(x, &sampleSize); + if (err != OK) { + return err; + } + + if (i == 0 || sampleSize > maxSampleSize) { + bestSampleIndex = x; + maxSampleSize = sampleSize; + } + } + + *sample_index = bestSampleIndex; + + return OK; +} + } // namespace android diff --git a/media/libstagefright/ShoutcastSource.cpp b/media/libstagefright/ShoutcastSource.cpp index 346b5aa..ec25430 100644 --- a/media/libstagefright/ShoutcastSource.cpp +++ b/media/libstagefright/ShoutcastSource.cpp @@ -14,16 +14,17 @@ * limitations under the License. */ +#include "include/stagefright_string.h" +#include "include/HTTPStream.h" + #include <stdlib.h> -#include <media/stagefright/HTTPStream.h> #include <media/stagefright/MediaBuffer.h> #include <media/stagefright/MediaBufferGroup.h> #include <media/stagefright/MediaDebug.h> #include <media/stagefright/MediaDefs.h> #include <media/stagefright/MetaData.h> #include <media/stagefright/ShoutcastSource.h> -#include <media/stagefright/stagefright_string.h> namespace android { diff --git a/media/libstagefright/TimedEventQueue.cpp b/media/libstagefright/TimedEventQueue.cpp index 3d85f75..fa68771 100644 --- a/media/libstagefright/TimedEventQueue.cpp +++ b/media/libstagefright/TimedEventQueue.cpp @@ -22,15 +22,17 @@ #define LOG_TAG "TimedEventQueue" #include <utils/Log.h> +#include "include/TimedEventQueue.h" + #include <sys/time.h> #include <media/stagefright/MediaDebug.h> -#include <media/stagefright/TimedEventQueue.h> namespace android { TimedEventQueue::TimedEventQueue() - : mRunning(false), + : mNextEventID(1), + mRunning(false), mStopped(false) { } @@ -75,26 +77,29 @@ void TimedEventQueue::stop(bool flush) { mRunning = false; } -void TimedEventQueue::postEvent(const sp<Event> &event) { +TimedEventQueue::event_id 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); + return postTimedEvent(event, INT64_MIN + 1); } -void TimedEventQueue::postEventToBack(const sp<Event> &event) { - postTimedEvent(event, INT64_MAX); +TimedEventQueue::event_id TimedEventQueue::postEventToBack( + const sp<Event> &event) { + return postTimedEvent(event, INT64_MAX); } -void TimedEventQueue::postEventWithDelay( +TimedEventQueue::event_id TimedEventQueue::postEventWithDelay( const sp<Event> &event, int64_t delay_us) { CHECK(delay_us >= 0); - postTimedEvent(event, getRealTimeUs() + delay_us); + return postTimedEvent(event, getRealTimeUs() + delay_us); } -void TimedEventQueue::postTimedEvent( +TimedEventQueue::event_id TimedEventQueue::postTimedEvent( const sp<Event> &event, int64_t realtime_us) { Mutex::Autolock autoLock(mLock); + event->setEventID(mNextEventID++); + List<QueueItem>::iterator it = mQueue.begin(); while (it != mQueue.end() && realtime_us >= (*it).realtime_us) { ++it; @@ -111,29 +116,60 @@ void TimedEventQueue::postTimedEvent( mQueue.insert(it, item); mQueueNotEmptyCondition.signal(); -} -bool TimedEventQueue::cancelEvent(const sp<Event> &event) { - Mutex::Autolock autoLock(mLock); + return event->eventID(); +} - List<QueueItem>::iterator it = mQueue.begin(); - while (it != mQueue.end() && (*it).event != event) { - ++it; - } +static bool MatchesEventID( + void *cookie, const sp<TimedEventQueue::Event> &event) { + TimedEventQueue::event_id *id = + static_cast<TimedEventQueue::event_id *>(cookie); - if (it == mQueue.end()) { + if (event->eventID() != *id) { return false; } - if (it == mQueue.begin()) { - mQueueHeadChangedCondition.signal(); - } - - mQueue.erase(it); + *id = 0; return true; } +bool TimedEventQueue::cancelEvent(event_id id) { + CHECK(id != 0); + + cancelEvents(&MatchesEventID, &id, true /* stopAfterFirstMatch */); + + // if MatchesEventID found a match, it will have set id to 0 + // (which is not a valid event_id). + + return id == 0; +} + +void TimedEventQueue::cancelEvents( + bool (*predicate)(void *cookie, const sp<Event> &event), + void *cookie, + bool stopAfterFirstMatch) { + Mutex::Autolock autoLock(mLock); + + List<QueueItem>::iterator it = mQueue.begin(); + while (it != mQueue.end()) { + if (!(*predicate)(cookie, (*it).event)) { + ++it; + continue; + } + + if (it == mQueue.begin()) { + mQueueHeadChangedCondition.signal(); + } + + it = mQueue.erase(it); + + if (stopAfterFirstMatch) { + return; + } + } +} + // static int64_t TimedEventQueue::getRealTimeUs() { struct timeval tv; diff --git a/media/libstagefright/WAVExtractor.cpp b/media/libstagefright/WAVExtractor.cpp new file mode 100644 index 0000000..542c764 --- /dev/null +++ b/media/libstagefright/WAVExtractor.cpp @@ -0,0 +1,317 @@ +/* + * 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 "WAVExtractor" +#include <utils/Log.h> + +#include "include/WAVExtractor.h" + +#include <media/stagefright/DataSource.h> +#include <media/stagefright/MediaBufferGroup.h> +#include <media/stagefright/MediaDebug.h> +#include <media/stagefright/MediaDefs.h> +#include <media/stagefright/MediaErrors.h> +#include <media/stagefright/MediaSource.h> +#include <media/stagefright/MetaData.h> +#include <utils/String8.h> + +namespace android { + +static uint16_t WAVE_FORMAT_PCM = 1; + +static uint32_t U32_LE_AT(const uint8_t *ptr) { + return ptr[3] << 24 | ptr[2] << 16 | ptr[1] << 8 | ptr[0]; +} + +static uint16_t U16_LE_AT(const uint8_t *ptr) { + return ptr[1] << 8 | ptr[0]; +} + +struct WAVSource : public MediaSource { + WAVSource( + const sp<DataSource> &dataSource, + int32_t sampleRate, int32_t numChannels, + off_t offset, size_t size); + + 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); + +protected: + virtual ~WAVSource(); + +private: + static const size_t kMaxFrameSize; + + sp<DataSource> mDataSource; + int32_t mSampleRate; + int32_t mNumChannels; + off_t mOffset; + size_t mSize; + bool mStarted; + MediaBufferGroup *mGroup; + off_t mCurrentPos; + + WAVSource(const WAVSource &); + WAVSource &operator=(const WAVSource &); +}; + +WAVExtractor::WAVExtractor(const sp<DataSource> &source) + : mDataSource(source), + mValidFormat(false) { + mInitCheck = init(); +} + +WAVExtractor::~WAVExtractor() { +} + +size_t WAVExtractor::countTracks() { + return mInitCheck == OK ? 1 : 0; +} + +sp<MediaSource> WAVExtractor::getTrack(size_t index) { + if (mInitCheck != OK || index > 0) { + return NULL; + } + + return new WAVSource( + mDataSource, mSampleRate, mNumChannels, mDataOffset, mDataSize); +} + +sp<MetaData> WAVExtractor::getTrackMetaData( + size_t index, uint32_t flags) { + if (mInitCheck != OK || index > 0) { + return NULL; + } + + sp<MetaData> meta = new MetaData; + meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_RAW); + meta->setInt32(kKeyChannelCount, mNumChannels); + meta->setInt32(kKeySampleRate, mSampleRate); + + int64_t durationUs = + 1000000LL * (mDataSize / (mNumChannels * 2)) / mSampleRate; + + meta->setInt64(kKeyDuration, durationUs); + + return meta; +} + +status_t WAVExtractor::init() { + uint8_t header[12]; + if (mDataSource->readAt( + 0, header, sizeof(header)) < (ssize_t)sizeof(header)) { + return NO_INIT; + } + + if (memcmp(header, "RIFF", 4) || memcmp(&header[8], "WAVE", 4)) { + return NO_INIT; + } + + size_t totalSize = U32_LE_AT(&header[4]); + + off_t offset = 12; + size_t remainingSize = totalSize; + while (remainingSize >= 8) { + uint8_t chunkHeader[8]; + if (mDataSource->readAt(offset, chunkHeader, 8) < 8) { + return NO_INIT; + } + + remainingSize -= 8; + offset += 8; + + uint32_t chunkSize = U32_LE_AT(&chunkHeader[4]); + + if (chunkSize > remainingSize) { + return NO_INIT; + } + + if (!memcmp(chunkHeader, "fmt ", 4)) { + if (chunkSize < 16) { + return NO_INIT; + } + + uint8_t formatSpec[16]; + if (mDataSource->readAt(offset, formatSpec, 16) < 16) { + return NO_INIT; + } + + uint16_t format = U16_LE_AT(formatSpec); + if (format != WAVE_FORMAT_PCM) { + return ERROR_UNSUPPORTED; + } + + mNumChannels = U16_LE_AT(&formatSpec[2]); + if (mNumChannels != 1 && mNumChannels != 2) { + return ERROR_UNSUPPORTED; + } + + mSampleRate = U32_LE_AT(&formatSpec[4]); + + if (U16_LE_AT(&formatSpec[14]) != 16) { + return ERROR_UNSUPPORTED; + } + + mValidFormat = true; + } else if (!memcmp(chunkHeader, "data", 4)) { + if (mValidFormat) { + mDataOffset = offset; + mDataSize = chunkSize; + + return OK; + } + } + + offset += chunkSize; + } + + return NO_INIT; +} + +const size_t WAVSource::kMaxFrameSize = 32768; + +WAVSource::WAVSource( + const sp<DataSource> &dataSource, + int32_t sampleRate, int32_t numChannels, + off_t offset, size_t size) + : mDataSource(dataSource), + mSampleRate(sampleRate), + mNumChannels(numChannels), + mOffset(offset), + mSize(size), + mStarted(false), + mGroup(NULL) { +} + +WAVSource::~WAVSource() { + if (mStarted) { + stop(); + } +} + +status_t WAVSource::start(MetaData *params) { + LOGV("WAVSource::start"); + + CHECK(!mStarted); + + mGroup = new MediaBufferGroup; + mGroup->add_buffer(new MediaBuffer(kMaxFrameSize)); + + mCurrentPos = mOffset; + + mStarted = true; + + return OK; +} + +status_t WAVSource::stop() { + LOGV("WAVSource::stop"); + + CHECK(mStarted); + + delete mGroup; + mGroup = NULL; + + mStarted = false; + + return OK; +} + +sp<MetaData> WAVSource::getFormat() { + LOGV("WAVSource::getFormat"); + + sp<MetaData> meta = new MetaData; + meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_RAW); + meta->setInt32(kKeyChannelCount, mNumChannels); + meta->setInt32(kKeySampleRate, mSampleRate); + + int64_t durationUs = + 1000000LL * (mSize / (mNumChannels * 2)) / mSampleRate; + + meta->setInt64(kKeyDuration, durationUs); + + return meta; +} + +status_t WAVSource::read( + MediaBuffer **out, const ReadOptions *options) { + *out = NULL; + + int64_t seekTimeUs; + if (options != NULL && options->getSeekTo(&seekTimeUs)) { + int64_t pos = (seekTimeUs * mSampleRate) / 1000000 * mNumChannels * 2; + if (pos > mSize) { + pos = mSize; + } + mCurrentPos = pos + mOffset; + } + + MediaBuffer *buffer; + status_t err = mGroup->acquire_buffer(&buffer); + if (err != OK) { + return err; + } + + ssize_t n = mDataSource->readAt( + mCurrentPos, buffer->data(), kMaxFrameSize); + + if (n <= 0) { + buffer->release(); + buffer = NULL; + + return ERROR_END_OF_STREAM; + } + + mCurrentPos += n; + + buffer->set_range(0, n); + buffer->meta_data()->setInt64( + kKeyTime, + 1000000LL * (mCurrentPos - mOffset) + / (mNumChannels * 2) / mSampleRate); + + + *out = buffer; + + return OK; +} + +//////////////////////////////////////////////////////////////////////////////// + +bool SniffWAV( + const sp<DataSource> &source, String8 *mimeType, float *confidence) { + char header[12]; + if (source->readAt(0, header, sizeof(header)) < (ssize_t)sizeof(header)) { + return false; + } + + if (memcmp(header, "RIFF", 4) || memcmp(&header[8], "WAVE", 4)) { + return false; + } + + *mimeType = MEDIA_MIMETYPE_CONTAINER_WAV; + *confidence = 0.3f; + + return true; +} + +} // namespace android + diff --git a/media/libstagefright/include/AMRExtractor.h b/media/libstagefright/include/AMRExtractor.h new file mode 100644 index 0000000..debf006 --- /dev/null +++ b/media/libstagefright/include/AMRExtractor.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 AMR_EXTRACTOR_H_ + +#define AMR_EXTRACTOR_H_ + +#include <media/stagefright/MediaExtractor.h> + +namespace android { + +class String8; + +class AMRExtractor : public MediaExtractor { +public: + AMRExtractor(const sp<DataSource> &source); + + virtual size_t countTracks(); + virtual sp<MediaSource> getTrack(size_t index); + virtual sp<MetaData> getTrackMetaData(size_t index, uint32_t flags); + + static sp<MetaData> makeAMRFormat(bool isWide); + +protected: + virtual ~AMRExtractor(); + +private: + sp<DataSource> mDataSource; + status_t mInitCheck; + bool mIsWide; + + AMRExtractor(const AMRExtractor &); + AMRExtractor &operator=(const AMRExtractor &); +}; + +bool SniffAMR( + const sp<DataSource> &source, String8 *mimeType, float *confidence); + +} // namespace android + +#endif // AMR_EXTRACTOR_H_ diff --git a/media/libstagefright/include/ESDS.h b/media/libstagefright/include/ESDS.h new file mode 100644 index 0000000..01bcd18 --- /dev/null +++ b/media/libstagefright/include/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/media/libstagefright/include/HTTPStream.h b/media/libstagefright/include/HTTPStream.h new file mode 100644 index 0000000..43ef614 --- /dev/null +++ b/media/libstagefright/include/HTTPStream.h @@ -0,0 +1,75 @@ +/* + * 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 "stagefright_string.h" + +#include <sys/types.h> + +#include <media/stagefright/MediaErrors.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/media/libstagefright/include/MP3Extractor.h b/media/libstagefright/include/MP3Extractor.h new file mode 100644 index 0000000..074973b --- /dev/null +++ b/media/libstagefright/include/MP3Extractor.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 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(const sp<DataSource> &source); + + virtual size_t countTracks(); + virtual sp<MediaSource> getTrack(size_t index); + virtual sp<MetaData> getTrackMetaData(size_t index, uint32_t flags); + +protected: + virtual ~MP3Extractor(); + +private: + sp<DataSource> mDataSource; + off_t mFirstFramePos; + sp<MetaData> mMeta; + uint32_t mFixedHeader; + + MP3Extractor(const MP3Extractor &); + MP3Extractor &operator=(const MP3Extractor &); +}; + +bool SniffMP3( + const sp<DataSource> &source, String8 *mimeType, float *confidence); + +} // namespace android + +#endif // MP3_EXTRACTOR_H_ diff --git a/media/libstagefright/include/MPEG4Extractor.h b/media/libstagefright/include/MPEG4Extractor.h new file mode 100644 index 0000000..ce4736d --- /dev/null +++ b/media/libstagefright/include/MPEG4Extractor.h @@ -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. + */ + +#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(const sp<DataSource> &source); + + size_t countTracks(); + sp<MediaSource> getTrack(size_t index); + sp<MetaData> getTrackMetaData(size_t index, uint32_t flags); + +protected: + virtual ~MPEG4Extractor(); + +private: + struct Track { + Track *next; + sp<MetaData> meta; + uint32_t timescale; + sp<SampleTable> sampleTable; + bool includes_expensive_metadata; + }; + + sp<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( + const sp<DataSource> &source, String8 *mimeType, float *confidence); + +} // namespace android + +#endif // MPEG4_EXTRACTOR_H_ diff --git a/media/libstagefright/include/OMX.h b/media/libstagefright/include/OMX.h index d0bd61e..a4b62b2 100644 --- a/media/libstagefright/include/OMX.h +++ b/media/libstagefright/include/OMX.h @@ -99,7 +99,7 @@ public: OMX_IN OMX_U32 nData1, OMX_IN OMX_U32 nData2, OMX_IN OMX_PTR pEventData); - + OMX_ERRORTYPE OnEmptyBufferDone( node_id node, OMX_IN OMX_BUFFERHEADERTYPE *pBuffer); diff --git a/media/libstagefright/include/SampleTable.h b/media/libstagefright/include/SampleTable.h new file mode 100644 index 0000000..ead3431 --- /dev/null +++ b/media/libstagefright/include/SampleTable.h @@ -0,0 +1,111 @@ +/* + * 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/RefBase.h> +#include <utils/threads.h> + +namespace android { + +class DataSource; + +class SampleTable : public RefBase { +public: + SampleTable(const sp<DataSource> &source); + + // type can be 'stco' or 'co64'. + status_t setChunkOffsetParams( + uint32_t type, off_t data_offset, size_t data_size); + + status_t setSampleToChunkParams(off_t data_offset, size_t data_size); + + // type can be 'stsz' or 'stz2'. + status_t setSampleSizeParams( + uint32_t type, off_t data_offset, size_t data_size); + + status_t setTimeToSampleParams(off_t data_offset, size_t data_size); + + status_t setSyncSampleParams(off_t data_offset, size_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); + + status_t findThumbnailSample(uint32_t *sample_index); + +protected: + ~SampleTable(); + +private: + sp<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/media/libstagefright/include/SoftwareRenderer.h b/media/libstagefright/include/SoftwareRenderer.h new file mode 100644 index 0000000..9eed089 --- /dev/null +++ b/media/libstagefright/include/SoftwareRenderer.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 SOFTWARE_RENDERER_H_ + +#define SOFTWARE_RENDERER_H_ + +#include <media/stagefright/ColorConverter.h> +#include <media/stagefright/VideoRenderer.h> +#include <utils/RefBase.h> + +namespace android { + +class ISurface; +class MemoryHeapBase; + +class SoftwareRenderer : public VideoRenderer { +public: + SoftwareRenderer( + OMX_COLOR_FORMATTYPE colorFormat, + 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: + OMX_COLOR_FORMATTYPE mColorFormat; + ColorConverter mConverter; + 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/media/libstagefright/include/TimedEventQueue.h b/media/libstagefright/include/TimedEventQueue.h new file mode 100644 index 0000000..21eade3 --- /dev/null +++ b/media/libstagefright/include/TimedEventQueue.h @@ -0,0 +1,130 @@ +/* + * 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 { + + typedef int32_t event_id; + + struct Event : public RefBase { + Event() + : mEventID(0) { + } + + virtual ~Event() {} + + event_id eventID() { + return mEventID; + } + + protected: + virtual void fire(TimedEventQueue *queue, int64_t now_us) = 0; + + private: + friend class TimedEventQueue; + + event_id mEventID; + + void setEventID(event_id id) { + mEventID = id; + } + + 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). + event_id postEvent(const sp<Event> &event); + + event_id postEventToBack(const sp<Event> &event); + + // It is an error to post an event with a negative delay. + event_id 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. + event_id 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(event_id id); + + // Cancel any pending event that satisfies the predicate. + // If stopAfterFirstMatch is true, only cancels the first event + // satisfying the predicate (if any). + void cancelEvents( + bool (*predicate)(void *cookie, const sp<Event> &event), + void *cookie, + bool stopAfterFirstMatch = false); + + 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; + event_id mNextEventID; + + 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/media/libstagefright/include/WAVExtractor.h b/media/libstagefright/include/WAVExtractor.h new file mode 100644 index 0000000..10b9700 --- /dev/null +++ b/media/libstagefright/include/WAVExtractor.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 WAV_EXTRACTOR_H_ + +#define WAV_EXTRACTOR_H_ + +#include <media/stagefright/MediaExtractor.h> + +namespace android { + +class DataSource; +class String8; + +class WAVExtractor : public MediaExtractor { +public: + // Extractor assumes ownership of "source". + WAVExtractor(const sp<DataSource> &source); + + virtual size_t countTracks(); + virtual sp<MediaSource> getTrack(size_t index); + virtual sp<MetaData> getTrackMetaData(size_t index, uint32_t flags); + +protected: + virtual ~WAVExtractor(); + +private: + sp<DataSource> mDataSource; + status_t mInitCheck; + bool mValidFormat; + uint16_t mNumChannels; + uint32_t mSampleRate; + off_t mDataOffset; + size_t mDataSize; + + status_t init(); + + WAVExtractor(const WAVExtractor &); + WAVExtractor &operator=(const WAVExtractor &); +}; + +bool SniffWAV( + const sp<DataSource> &source, String8 *mimeType, float *confidence); + +} // namespace android + +#endif // WAV_EXTRACTOR_H_ + diff --git a/media/libstagefright/include/stagefright_string.h b/media/libstagefright/include/stagefright_string.h new file mode 100644 index 0000000..5dc7116 --- /dev/null +++ b/media/libstagefright/include/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/libstagefright/omx/Android.mk b/media/libstagefright/omx/Android.mk index 25da813..389c2c9 100644 --- a/media/libstagefright/omx/Android.mk +++ b/media/libstagefright/omx/Android.mk @@ -9,6 +9,7 @@ LOCAL_CFLAGS := $(PV_CFLAGS_MINUS_VISIBILITY) LOCAL_C_INCLUDES += $(JNI_H_INCLUDE) LOCAL_SRC_FILES:= \ + ColorConverter.cpp \ OMX.cpp \ OMXNodeInstance.cpp \ SoftwareRenderer.cpp diff --git a/media/libstagefright/omx/ColorConverter.cpp b/media/libstagefright/omx/ColorConverter.cpp new file mode 100644 index 0000000..e74782f --- /dev/null +++ b/media/libstagefright/omx/ColorConverter.cpp @@ -0,0 +1,297 @@ +/* + * 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/ColorConverter.h> +#include <media/stagefright/MediaDebug.h> + +namespace android { + +static const int OMX_QCOM_COLOR_FormatYVU420SemiPlanar = 0x7FA30C00; + +ColorConverter::ColorConverter( + OMX_COLOR_FORMATTYPE from, OMX_COLOR_FORMATTYPE to) + : mSrcFormat(from), + mDstFormat(to), + mClip(NULL) { +} + +ColorConverter::~ColorConverter() { + delete[] mClip; + mClip = NULL; +} + +bool ColorConverter::isValid() const { + if (mDstFormat != OMX_COLOR_Format16bitRGB565) { + return false; + } + + switch (mSrcFormat) { + case OMX_COLOR_FormatYUV420Planar: + case OMX_COLOR_FormatCbYCrY: + case OMX_QCOM_COLOR_FormatYVU420SemiPlanar: + return true; + + default: + return false; + } +} + +void ColorConverter::convert( + size_t width, size_t height, + const void *srcBits, size_t srcSkip, + void *dstBits, size_t dstSkip) { + CHECK_EQ(mDstFormat, OMX_COLOR_Format16bitRGB565); + + switch (mSrcFormat) { + case OMX_COLOR_FormatYUV420Planar: + convertYUV420Planar( + width, height, srcBits, srcSkip, dstBits, dstSkip); + break; + + case OMX_COLOR_FormatCbYCrY: + convertCbYCrY( + width, height, srcBits, srcSkip, dstBits, dstSkip); + break; + + case OMX_QCOM_COLOR_FormatYVU420SemiPlanar: + convertQCOMYUV420SemiPlanar( + width, height, srcBits, srcSkip, dstBits, dstSkip); + break; + + default: + { + CHECK(!"Should not be here. Unknown color conversion."); + break; + } + } +} + +void ColorConverter::convertCbYCrY( + size_t width, size_t height, + const void *srcBits, size_t srcSkip, + void *dstBits, size_t dstSkip) { + CHECK_EQ(srcSkip, 0); // Doesn't really make sense for YUV formats. + CHECK(dstSkip >= width * 2); + CHECK((dstSkip & 3) == 0); + + uint8_t *kAdjustedClip = initClip(); + + uint32_t *dst_ptr = (uint32_t *)dstBits; + + const uint8_t *src = (const uint8_t *)srcBits; + + for (size_t y = 0; y < height; ++y) { + for (size_t x = 0; x < width; x += 2) { + signed y1 = (signed)src[2 * x + 1] - 16; + signed y2 = (signed)src[2 * x + 3] - 16; + signed u = (signed)src[2 * x] - 128; + signed v = (signed)src[2 * x + 2] - 128; + + 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 += width * 2; + dst_ptr += dstSkip / 4; + } +} + +void ColorConverter::convertYUV420Planar( + size_t width, size_t height, + const void *srcBits, size_t srcSkip, + void *dstBits, size_t dstSkip) { + CHECK_EQ(srcSkip, 0); // Doesn't really make sense for YUV formats. + CHECK(dstSkip >= width * 2); + CHECK((dstSkip & 3) == 0); + + uint8_t *kAdjustedClip = initClip(); + + uint32_t *dst_ptr = (uint32_t *)dstBits; + const uint8_t *src_y = (const uint8_t *)srcBits; + + const uint8_t *src_u = + (const uint8_t *)src_y + width * height; + + const uint8_t *src_v = + (const uint8_t *)src_u + (width / 2) * (height / 2); + + for (size_t y = 0; y < height; ++y) { + for (size_t x = 0; x < width; 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; + + signed u = (signed)src_u[x / 2] - 128; + signed v = (signed)src_v[x / 2] - 128; + + 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 += width; + + if (y & 1) { + src_u += width / 2; + src_v += width / 2; + } + + dst_ptr += dstSkip / 4; + } +} + +void ColorConverter::convertQCOMYUV420SemiPlanar( + size_t width, size_t height, + const void *srcBits, size_t srcSkip, + void *dstBits, size_t dstSkip) { + CHECK_EQ(srcSkip, 0); // Doesn't really make sense for YUV formats. + CHECK(dstSkip >= width * 2); + CHECK((dstSkip & 3) == 0); + + uint8_t *kAdjustedClip = initClip(); + + uint32_t *dst_ptr = (uint32_t *)dstBits; + const uint8_t *src_y = (const uint8_t *)srcBits; + + const uint8_t *src_u = + (const uint8_t *)src_y + width * height; + + for (size_t y = 0; y < height; ++y) { + for (size_t x = 0; x < width; x += 2) { + signed y1 = (signed)src_y[x] - 16; + signed y2 = (signed)src_y[x + 1] - 16; + + signed u = (signed)src_u[x & ~1] - 128; + signed v = (signed)src_u[(x & ~1) + 1] - 128; + + 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[b1] >> 3) << 11) + | ((kAdjustedClip[g1] >> 2) << 5) + | (kAdjustedClip[r1] >> 3); + + uint32_t rgb2 = + ((kAdjustedClip[b2] >> 3) << 11) + | ((kAdjustedClip[g2] >> 2) << 5) + | (kAdjustedClip[r2] >> 3); + + dst_ptr[x / 2] = (rgb2 << 16) | rgb1; + } + + src_y += width; + + if (y & 1) { + src_u += width; + } + + dst_ptr += dstSkip / 4; + } +} + +uint8_t *ColorConverter::initClip() { + static const signed kClipMin = -278; + static const signed kClipMax = 535; + + if (mClip == NULL) { + mClip = new uint8_t[kClipMax - kClipMin + 1]; + + for (signed i = kClipMin; i <= kClipMax; ++i) { + mClip[i - kClipMin] = (i < 0) ? 0 : (i > 255) ? 255 : (uint8_t)i; + } + } + + return &mClip[-kClipMin]; +} + +} // namespace android diff --git a/media/libstagefright/omx/OMX.cpp b/media/libstagefright/omx/OMX.cpp index 4ccd4bd..5b3cc1b 100644 --- a/media/libstagefright/omx/OMX.cpp +++ b/media/libstagefright/omx/OMX.cpp @@ -24,10 +24,10 @@ #include "pv_omxcore.h" #include "../include/OMXNodeInstance.h" +#include "../include/SoftwareRenderer.h" #include <binder/IMemory.h> #include <media/stagefright/MediaDebug.h> -#include <media/stagefright/SoftwareRenderer.h> #include <media/stagefright/VideoRenderer.h> #include <OMX_Component.h> @@ -233,7 +233,7 @@ status_t OMX::allocateNode( &OMXNodeInstance::kCallbacks); if (err != OMX_ErrorNone) { - LOGE("FAILED to allocate omx component '%s'", name); + LOGV("FAILED to allocate omx component '%s'", name); instance->onGetHandleFailed(); diff --git a/media/libstagefright/omx/SoftwareRenderer.cpp b/media/libstagefright/omx/SoftwareRenderer.cpp index 39de504..ef6ede0 100644 --- a/media/libstagefright/omx/SoftwareRenderer.cpp +++ b/media/libstagefright/omx/SoftwareRenderer.cpp @@ -17,21 +17,21 @@ #define LOG_TAG "SoftwareRenderer" #include <utils/Log.h> +#include "../include/SoftwareRenderer.h" + #include <binder/MemoryHeapBase.h> #include <media/stagefright/MediaDebug.h> -#include <media/stagefright/SoftwareRenderer.h> #include <ui/ISurface.h> namespace android { -#define QCOM_YUV 0 - SoftwareRenderer::SoftwareRenderer( OMX_COLOR_FORMATTYPE colorFormat, const sp<ISurface> &surface, size_t displayWidth, size_t displayHeight, size_t decodedWidth, size_t decodedHeight) : mColorFormat(colorFormat), + mConverter(colorFormat, OMX_COLOR_Format16bitRGB565), mISurface(surface), mDisplayWidth(displayWidth), mDisplayHeight(displayHeight), @@ -39,12 +39,12 @@ SoftwareRenderer::SoftwareRenderer( mDecodedHeight(decodedHeight), mFrameSize(mDecodedWidth * mDecodedHeight * 2), // RGB565 mMemoryHeap(new MemoryHeapBase(2 * mFrameSize)), - mIndex(0), - mClip(NULL) { + mIndex(0) { CHECK(mISurface.get() != NULL); CHECK(mDecodedWidth > 0); CHECK(mDecodedHeight > 0); CHECK(mMemoryHeap->heapID() >= 0); + CHECK(mConverter.isValid()); ISurface::BufferHeap bufferHeap( mDisplayWidth, mDisplayHeight, @@ -58,278 +58,19 @@ SoftwareRenderer::SoftwareRenderer( SoftwareRenderer::~SoftwareRenderer() { mISurface->unregisterBuffers(); - - delete[] mClip; - mClip = NULL; } void SoftwareRenderer::render( const void *data, size_t size, void *platformPrivate) { - static const int OMX_QCOM_COLOR_FormatYVU420SemiPlanar = 0x7FA30C00; - - switch (mColorFormat) { - case OMX_COLOR_FormatYUV420Planar: - return renderYUV420Planar(data, size); - - case OMX_COLOR_FormatCbYCrY: - return renderCbYCrY(data, size); - - case OMX_QCOM_COLOR_FormatYVU420SemiPlanar: - return renderQCOMYUV420SemiPlanar(data, size); - - default: - { - LOGW("Cannot render color format %d", mColorFormat); - break; - } - } -} - -void SoftwareRenderer::renderYUV420Planar( - const void *data, size_t size) { - if (size != (mDecodedHeight * mDecodedWidth * 3) / 2) { - LOGE("size is %d, expected %d", - size, (mDecodedHeight * mDecodedWidth * 3) / 2); - } - CHECK(size >= (mDecodedWidth * mDecodedHeight * 3) / 2); - - uint8_t *kAdjustedClip = initClip(); - 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; -} - -void SoftwareRenderer::renderCbYCrY( - const void *data, size_t size) { - if (size != (mDecodedHeight * mDecodedWidth * 2)) { - LOGE("size is %d, expected %d", - size, (mDecodedHeight * mDecodedWidth * 2)); - } - CHECK(size >= (mDecodedWidth * mDecodedHeight * 2)); - - uint8_t *kAdjustedClip = initClip(); - - size_t offset = mIndex * mFrameSize; - void *dst = (uint8_t *)mMemoryHeap->getBase() + offset; - uint32_t *dst_ptr = (uint32_t *)dst; - - const uint8_t *src = (const uint8_t *)data; - - for (size_t y = 0; y < mDecodedHeight; ++y) { - for (size_t x = 0; x < mDecodedWidth; x += 2) { - signed y1 = (signed)src[2 * x + 1] - 16; - signed y2 = (signed)src[2 * x + 3] - 16; - signed u = (signed)src[2 * x] - 128; - signed v = (signed)src[2 * x + 2] - 128; - - 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 += mDecodedWidth * 2; - dst_ptr += mDecodedWidth / 2; - } - - mISurface->postBuffer(offset); - mIndex = 1 - mIndex; -} - -void SoftwareRenderer::renderQCOMYUV420SemiPlanar( - const void *data, size_t size) { - if (size != (mDecodedHeight * mDecodedWidth * 3) / 2) { - LOGE("size is %d, expected %d", - size, (mDecodedHeight * mDecodedWidth * 3) / 2); - } - CHECK(size >= (mDecodedWidth * mDecodedHeight * 3) / 2); - - uint8_t *kAdjustedClip = initClip(); - - 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; - - for (size_t y = 0; y < mDecodedHeight; ++y) { - for (size_t x = 0; x < mDecodedWidth; x += 2) { - signed y1 = (signed)src_y[x] - 16; - signed y2 = (signed)src_y[x + 1] - 16; - - signed u = (signed)src_u[x & ~1] - 128; - signed v = (signed)src_u[(x & ~1) + 1] - 128; - - 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[b1] >> 3) << 11) - | ((kAdjustedClip[g1] >> 2) << 5) - | (kAdjustedClip[r1] >> 3); - - uint32_t rgb2 = - ((kAdjustedClip[b2] >> 3) << 11) - | ((kAdjustedClip[g2] >> 2) << 5) - | (kAdjustedClip[r2] >> 3); - - dst_ptr[x / 2] = (rgb2 << 16) | rgb1; - } - - src_y += mDecodedWidth; - - if (y & 1) { - src_u += mDecodedWidth; - } - - dst_ptr += mDecodedWidth / 2; - } + mConverter.convert( + mDecodedWidth, mDecodedHeight, + data, 0, dst, 2 * mDecodedWidth); mISurface->postBuffer(offset); mIndex = 1 - mIndex; } -uint8_t *SoftwareRenderer::initClip() { - static const signed kClipMin = -278; - static const signed kClipMax = 535; - - if (mClip == NULL) { - mClip = new uint8_t[kClipMax - kClipMin + 1]; - - for (signed i = kClipMin; i <= kClipMax; ++i) { - mClip[i - kClipMin] = (i < 0) ? 0 : (i > 255) ? 255 : (uint8_t)i; - } - } - - return &mClip[-kClipMin]; -} - } // namespace android diff --git a/media/libstagefright/stagefright_string.cpp b/media/libstagefright/string.cpp index 2aedb80..bd6204b 100644 --- a/media/libstagefright/stagefright_string.cpp +++ b/media/libstagefright/string.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include <media/stagefright/stagefright_string.h> +#include "include/stagefright_string.h" namespace android { diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/CameraTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/CameraTest.java index e66e560..fa0986a 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/CameraTest.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/CameraTest.java @@ -226,8 +226,7 @@ public class CameraTest extends ActivityInstrumentationTestCase<MediaFrameworkTe * Test case 1: Take a picture and verify all the callback * functions are called properly. */ - // TODO: add this back to LargeTest once bug 2141755 is fixed - // @LargeTest + @LargeTest public void testTakePicture() throws Exception { synchronized (lock) { initializeMessageLooper(); |