From 0c3be875376adaee8d8e8dd917c64926e1513b29 Mon Sep 17 00:00:00 2001 From: Marco Nelissen Date: Thu, 1 May 2014 10:14:44 -0700 Subject: WIP: MediaCodec and friends NDK APIs, plain C version Change-Id: I9ed6b9c5afb026a1b5fe8b652e75635bbcc223df --- media/ndk/Android.mk | 41 +++++++ media/ndk/NdkMediaCodec.cpp | 237 ++++++++++++++++++++++++++++++++++++++++ media/ndk/NdkMediaExtractor.cpp | 189 ++++++++++++++++++++++++++++++++ media/ndk/NdkMediaFormat.cpp | 198 +++++++++++++++++++++++++++++++++ media/ndk/NdkMediaFormatPriv.h | 44 ++++++++ 5 files changed, 709 insertions(+) create mode 100644 media/ndk/Android.mk create mode 100644 media/ndk/NdkMediaCodec.cpp create mode 100644 media/ndk/NdkMediaExtractor.cpp create mode 100644 media/ndk/NdkMediaFormat.cpp create mode 100644 media/ndk/NdkMediaFormatPriv.h (limited to 'media/ndk') diff --git a/media/ndk/Android.mk b/media/ndk/Android.mk new file mode 100644 index 0000000..0ea0c3f --- /dev/null +++ b/media/ndk/Android.mk @@ -0,0 +1,41 @@ +# +# Copyright (C) 2010 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +LOCAL_PATH:= $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + NdkMediaCodec.cpp \ + NdkMediaExtractor.cpp \ + NdkMediaFormat.cpp \ + +LOCAL_MODULE:= libmediandk + +LOCAL_C_INCLUDES := \ + bionic/libc/private \ + frameworks/base/core/jni \ + frameworks/av/include/ndk + +LOCAL_SHARED_LIBRARIES := \ + libmedia \ + libstagefright \ + libstagefright_foundation \ + liblog \ + libutils \ + libandroid_runtime \ + +include $(BUILD_SHARED_LIBRARY) diff --git a/media/ndk/NdkMediaCodec.cpp b/media/ndk/NdkMediaCodec.cpp new file mode 100644 index 0000000..5412b9b --- /dev/null +++ b/media/ndk/NdkMediaCodec.cpp @@ -0,0 +1,237 @@ +/* + * Copyright (C) 2014 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 "NdkMediaCodec" + +#include "NdkMediaCodec.h" +#include "NdkMediaFormatPriv.h" + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +using namespace android; + + +static int translate_error(status_t err) { + if (err == OK) { + return OK; + } else if (err == -EAGAIN) { + return AMEDIACODEC_INFO_TRY_AGAIN_LATER; + } + ALOGE("sf error code: %d", err); + return -1000; +} + + +class CodecHandler: public AHandler { +public: + CodecHandler(sp); + virtual void onMessageReceived(const sp &msg); +}; + +CodecHandler::CodecHandler(sp) { + +} + +void CodecHandler::onMessageReceived(const sp &msg) { + ALOGI("handler got message %d", msg->what()); +} + +struct AMediaCodec { + sp mCodec; + sp mLooper; + sp mHandler; +}; + +extern "C" { + +static AMediaCodec * createAMediaCodec(const char *name, bool name_is_type, bool encoder) { + AMediaCodec *mData = new AMediaCodec(); + mData->mLooper = new ALooper; + mData->mLooper->setName("NDK MediaCodec_looper"); + status_t ret = mData->mLooper->start( + false, // runOnCallingThread + true, // canCallJava XXX + PRIORITY_FOREGROUND); + ALOGV("looper start: %d", ret); + if (name_is_type) { + mData->mCodec = android::MediaCodec::CreateByType(mData->mLooper, name, encoder); + } else { + mData->mCodec = android::MediaCodec::CreateByComponentName(mData->mLooper, name); + } + mData->mHandler = new CodecHandler(mData->mCodec); + mData->mLooper->registerHandler(mData->mHandler); + return mData; +} + + +AMediaCodec* AMediaCodec_createByCodecName(const char *name) { + return createAMediaCodec(name, false, false); +} + +AMediaCodec* AMediaCodec_createByCodecType(const char *mime_type) { + return createAMediaCodec(mime_type, true, false); +} + +AMediaCodec* AMediaCodec_createEncoderByName(const char *name) { + return createAMediaCodec(name, false, true); +} + +int AMediaCodec_delete(AMediaCodec *mData) { + if (mData->mCodec != NULL) { + mData->mCodec->release(); + mData->mCodec.clear(); + } + + if (mData->mLooper != NULL) { + mData->mLooper->unregisterHandler(mData->mHandler->id()); + mData->mLooper->stop(); + mData->mLooper.clear(); + } + delete mData; + return OK; +} + +int AMediaCodec_configure(AMediaCodec *mData, AMediaFormat *format, ANativeWindow* window) { + sp nativeFormat; + AMediaFormat_getFormat(format, &nativeFormat); + ALOGV("configure with format: %s", nativeFormat->debugString(0).c_str()); + sp surface = NULL; + if (window != NULL) { + surface = (Surface*) window; + } + + return translate_error(mData->mCodec->configure(nativeFormat, surface, NULL, 0)); +} + +int AMediaCodec_start(AMediaCodec *mData) { + return translate_error(mData->mCodec->start()); +} + +int AMediaCodec_stop(AMediaCodec *mData) { + return translate_error(mData->mCodec->stop()); +} + +int AMediaCodec_flush(AMediaCodec *mData) { + return translate_error(mData->mCodec->flush()); +} + +ssize_t AMediaCodec_dequeueInputBuffer(AMediaCodec *mData, int64_t timeoutUs) { + size_t idx; + status_t ret = mData->mCodec->dequeueInputBuffer(&idx, timeoutUs); + if (ret == OK) { + return idx; + } + return translate_error(ret); +} + +uint8_t* AMediaCodec_getInputBuffer(AMediaCodec *mData, size_t idx, size_t *out_size) { + android::Vector > abufs; + if (mData->mCodec->getInputBuffers(&abufs) == 0) { + size_t n = abufs.size(); + if (idx >= n) { + ALOGE("buffer index %d out of range", idx); + return NULL; + } + if (out_size != NULL) { + *out_size = abufs[idx]->capacity(); + } + return abufs[idx]->data(); + } + ALOGE("couldn't get input buffers"); + return NULL; +} + +uint8_t* AMediaCodec_getOutputBuffer(AMediaCodec *mData, size_t idx, size_t *out_size) { + android::Vector > abufs; + if (mData->mCodec->getOutputBuffers(&abufs) == 0) { + size_t n = abufs.size(); + if (idx >= n) { + ALOGE("buffer index %d out of range", idx); + return NULL; + } + if (out_size != NULL) { + *out_size = abufs[idx]->capacity(); + } + return abufs[idx]->data(); + } + ALOGE("couldn't get output buffers"); + return NULL; +} + +int AMediaCodec_queueInputBuffer(AMediaCodec *mData, + size_t idx, off_t offset, size_t size, uint64_t time, uint32_t flags) { + + AString errorMsg; + status_t ret = mData->mCodec->queueInputBuffer(idx, offset, size, time, flags, &errorMsg); + return translate_error(ret); +} + +ssize_t AMediaCodec_dequeueOutputBuffer(AMediaCodec *mData, + AMediaCodecBufferInfo *info, int64_t timeoutUs) { + size_t idx; + size_t offset; + size_t size; + uint32_t flags; + int64_t presentationTimeUs; + status_t ret = mData->mCodec->dequeueOutputBuffer(&idx, &offset, &size, &presentationTimeUs, + &flags, timeoutUs); + + switch (ret) { + case OK: + info->offset = offset; + info->size = size; + info->flags = flags; + info->presentationTimeUs = presentationTimeUs; + return idx; + case -EAGAIN: + return AMEDIACODEC_INFO_TRY_AGAIN_LATER; + case android::INFO_FORMAT_CHANGED: + return AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED; + case INFO_OUTPUT_BUFFERS_CHANGED: + return AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED; + default: + break; + } + return translate_error(ret); +} + +AMediaFormat* AMediaCodec_getOutputFormat(AMediaCodec *mData) { + sp format; + mData->mCodec->getOutputFormat(&format); + return AMediaFormat_fromMsg(&format); +} + +int AMediaCodec_releaseOutputBuffer(AMediaCodec *mData, size_t idx, bool render) { + if (render) { + return translate_error(mData->mCodec->renderOutputBufferAndRelease(idx)); + } else { + return translate_error(mData->mCodec->releaseOutputBuffer(idx)); + } +} + +} // extern "C" + diff --git a/media/ndk/NdkMediaExtractor.cpp b/media/ndk/NdkMediaExtractor.cpp new file mode 100644 index 0000000..681633a --- /dev/null +++ b/media/ndk/NdkMediaExtractor.cpp @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2014 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 "NdkMediaExtractor" + + +#include "NdkMediaExtractor.h" +#include "NdkMediaFormatPriv.h" + + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace android; + +static int translate_error(status_t err) { + if (err == OK) { + return OK; + } + ALOGE("sf error code: %d", err); + return -1000; +} + +struct AMediaExtractor { + sp mImpl; + +}; + +extern "C" { + +AMediaExtractor* AMediaExtractor_new() { + ALOGV("ctor"); + AMediaExtractor *mData = new AMediaExtractor(); + mData->mImpl = new NuMediaExtractor(); + return mData; +} + +int AMediaExtractor_delete(AMediaExtractor *mData) { + ALOGV("dtor"); + delete mData; + return OK; +} + +int AMediaExtractor_setDataSourceFd(AMediaExtractor *mData, int fd, off64_t offset, off64_t length) { + ALOGV("setDataSource(%d, %lld, %lld)", fd, offset, length); + mData->mImpl->setDataSource(fd, offset, length); + return 0; +} + +int AMediaExtractor_setDataSource(AMediaExtractor *mData, const char *location) { + ALOGV("setDataSource(%s)", location); + // TODO: add header support + + JNIEnv *env = AndroidRuntime::getJNIEnv(); + jobject service = NULL; + if (env == NULL) { + ALOGE("setDataSource(path) must be called from Java thread"); + env->ExceptionClear(); + return -1; + } + + jclass mediahttpclass = env->FindClass("android/media/MediaHTTPService"); + if (mediahttpclass == NULL) { + ALOGE("can't find MediaHttpService"); + env->ExceptionClear(); + return -1; + } + + jmethodID mediaHttpCreateMethod = env->GetStaticMethodID(mediahttpclass, + "createHttpServiceBinderIfNecessary", "(Ljava/lang/String;)Landroid/os/IBinder;"); + if (mediaHttpCreateMethod == NULL) { + ALOGE("can't find method"); + env->ExceptionClear(); + return -1; + } + + jstring jloc = env->NewStringUTF(location); + + service = env->CallStaticObjectMethod(mediahttpclass, mediaHttpCreateMethod, jloc); + env->DeleteLocalRef(jloc); + + sp httpService; + if (service != NULL) { + sp binder = ibinderForJavaObject(env, service); + httpService = interface_cast(binder); + } + + mData->mImpl->setDataSource(httpService, location, NULL); + env->ExceptionClear(); + return 0; +} + +int AMediaExtractor_getTrackCount(AMediaExtractor *mData) { + return mData->mImpl->countTracks(); +} + +AMediaFormat* AMediaExtractor_getTrackFormat(AMediaExtractor *mData, size_t idx) { + sp format; + mData->mImpl->getTrackFormat(idx, &format); + return AMediaFormat_fromMsg(&format); +} + +int AMediaExtractor_selectTrack(AMediaExtractor *mData, size_t idx) { + ALOGV("selectTrack(%z)", idx); + return translate_error(mData->mImpl->selectTrack(idx)); +} + +int AMediaExtractor_unselectTrack(AMediaExtractor *mData, size_t idx) { + ALOGV("unselectTrack(%z)", idx); + return translate_error(mData->mImpl->unselectTrack(idx)); +} + +bool AMediaExtractor_advance(AMediaExtractor *mData) { + //ALOGV("advance"); + return mData->mImpl->advance(); +} + +int AMediaExtractor_readSampleData(AMediaExtractor *mData, uint8_t *buffer, size_t capacity) { + //ALOGV("readSampleData"); + sp tmp = new ABuffer(buffer, capacity); + if (mData->mImpl->readSampleData(tmp) == OK) { + return tmp->size(); + } + return -1; +} + +int AMediaExtractor_getSampleFlags(AMediaExtractor *mData) { + int sampleFlags = 0; + sp meta; + status_t err = mData->mImpl->getSampleMeta(&meta); + if (err != OK) { + return -1; + } + int32_t val; + if (meta->findInt32(kKeyIsSyncFrame, &val) && val != 0) { + sampleFlags |= NuMediaExtractor::SAMPLE_FLAG_SYNC; + } + + uint32_t type; + const void *data; + size_t size; + if (meta->findData(kKeyEncryptedSizes, &type, &data, &size)) { + sampleFlags |= NuMediaExtractor::SAMPLE_FLAG_ENCRYPTED; + } + return sampleFlags; +} + +int AMediaExtractor_getSampleTrackIndex(AMediaExtractor *mData) { + size_t idx; + if (mData->mImpl->getSampleTrackIndex(&idx) != OK) { + return -1; + } + return idx; +} + +int64_t AMediaExtractor_getSampletime(AMediaExtractor *mData) { + int64_t time; + if (mData->mImpl->getSampleTime(&time) != OK) { + return -1; + } + return time; +} + + +} // extern "C" + diff --git a/media/ndk/NdkMediaFormat.cpp b/media/ndk/NdkMediaFormat.cpp new file mode 100644 index 0000000..359f37e --- /dev/null +++ b/media/ndk/NdkMediaFormat.cpp @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2014 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 "NdkMediaFormat" + + +#include "NdkMediaFormat.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace android; + +struct AMediaFormat { + sp mFormat; +}; + +extern "C" { + +// private functions for conversion to/from AMessage +AMediaFormat* AMediaFormat_fromMsg(void* data) { + ALOGV("private ctor"); + AMediaFormat* mData = new AMediaFormat(); + mData->mFormat = *((sp*)data); + return mData; +} + +void AMediaFormat_getFormat(AMediaFormat* mData, void* dest) { + *((sp*)dest) = mData->mFormat; +} + + +/* + * public function follow + */ +AMediaFormat *AMediaFormat_new() { + ALOGV("ctor"); + sp msg = new AMessage(); + return AMediaFormat_fromMsg(&msg); +} + +int AMediaFormat_delete(AMediaFormat *mData) { + ALOGV("dtor"); + delete mData; + return OK; +} + + +const char* AMediaFormat_toString(AMediaFormat *mData) { + sp f = mData->mFormat; + String8 ret; + int num = f->countEntries(); + for (int i = 0; i < num; i++) { + if (i != 0) { + ret.append(", "); + } + AMessage::Type t; + const char *name = f->getEntryNameAt(i, &t); + ret.append(name); + ret.append(": "); + switch (t) { + case AMessage::kTypeInt32: + { + int32_t val; + f->findInt32(name, &val); + ret.appendFormat("int32(%d)", val); + break; + } + case AMessage::kTypeInt64: + { + int64_t val; + f->findInt64(name, &val); + ret.appendFormat("int64(%lld)", val); + break; + } + case AMessage::kTypeSize: + { + size_t val; + f->findSize(name, &val); + ret.appendFormat("size_t(%d)", val); + break; + } + case AMessage::kTypeFloat: + { + float val; + f->findFloat(name, &val); + ret.appendFormat("float(%f)", val); + break; + } + case AMessage::kTypeDouble: + { + double val; + f->findDouble(name, &val); + ret.appendFormat("double(%f)", val); + break; + } + case AMessage::kTypeString: + { + AString val; + f->findString(name, &val); + ret.appendFormat("string(%s)", val.c_str()); + break; + } + case AMessage::kTypeBuffer: + { + ret.appendFormat("data"); + break; + } + default: + { + ret.appendFormat("unknown(%d)", t); + break; + } + } + } + ret.append("}"); + return strdup(ret.string()); +} + +bool AMediaFormat_getInt32(AMediaFormat* mData, const char *name, int32_t *out) { + return mData->mFormat->findInt32(name, out); +} + +bool AMediaFormat_getInt64(AMediaFormat* mData, const char *name, int64_t *out) { + return mData->mFormat->findInt64(name, out); +} + +bool AMediaFormat_getFloat(AMediaFormat* mData, const char *name, float *out) { + return mData->mFormat->findFloat(name, out); +} + +bool AMediaFormat_getDouble(AMediaFormat* mData, const char *name, double *out) { + return mData->mFormat->findDouble(name, out); +} + +bool AMediaFormat_getSize(AMediaFormat* mData, const char *name, size_t *out) { + return mData->mFormat->findSize(name, out); +} + +bool AMediaFormat_getString(AMediaFormat* mData, const char *name, const char **out) { + AString tmp; + if (mData->mFormat->findString(name, &tmp)) { + *out = strdup(tmp.c_str()); + return true; + } + return false; +} + +const char* AMEDIAFORMAT_KEY_AAC_PROFILE = "aac-profile"; +const char* AMEDIAFORMAT_KEY_BIT_RATE = "bitrate"; +const char* AMEDIAFORMAT_KEY_CHANNEL_COUNT = "channel-count"; +const char* AMEDIAFORMAT_KEY_CHANNEL_MASK = "channel-mask"; +const char* AMEDIAFORMAT_KEY_COLOR_FORMAT = "color-format"; +const char* AMEDIAFORMAT_KEY_DURATION = "durationUs"; +const char* AMEDIAFORMAT_KEY_FLAC_COMPRESSION_LEVEL = "flac-compression-level"; +const char* AMEDIAFORMAT_KEY_FRAME_RATE = "frame-rate"; +const char* AMEDIAFORMAT_KEY_HEIGHT = "height"; +const char* AMEDIAFORMAT_KEY_IS_ADTS = "is-adts"; +const char* AMEDIAFORMAT_KEY_IS_AUTOSELECT = "is-autoselect"; +const char* AMEDIAFORMAT_KEY_IS_DEFAULT = "is-default"; +const char* AMEDIAFORMAT_KEY_IS_FORCED_SUBTITLE = "is-forced-subtitle"; +const char* AMEDIAFORMAT_KEY_I_FRAME_INTERVAL = "i-frame-interval"; +const char* AMEDIAFORMAT_KEY_LANGUAGE = "language"; +const char* AMEDIAFORMAT_KEY_MAX_HEIGHT = "max-height"; +const char* AMEDIAFORMAT_KEY_MAX_INPUT_SIZE = "max-input-size"; +const char* AMEDIAFORMAT_KEY_MAX_WIDTH = "max-width"; +const char* AMEDIAFORMAT_KEY_MIME = "mime"; +const char* AMEDIAFORMAT_KEY_PUSH_BLANK_BUFFERS_ON_STOP = "push-blank-buffers-on-shutdown"; +const char* AMEDIAFORMAT_KEY_REPEAT_PREVIOUS_FRAME_AFTER = "repeat-previous-frame-after"; +const char* AMEDIAFORMAT_KEY_SAMPLE_RATE = "sample-rate"; +const char* AMEDIAFORMAT_KEY_WIDTH = "width"; +const char* AMEDIAFORMAT_KEY_STRIDE = "stride"; + + +} // extern "C" + + diff --git a/media/ndk/NdkMediaFormatPriv.h b/media/ndk/NdkMediaFormatPriv.h new file mode 100644 index 0000000..f67e782 --- /dev/null +++ b/media/ndk/NdkMediaFormatPriv.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2014 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. + */ + +/* + * This file defines an NDK API. + * Do not remove methods. + * Do not change method signatures. + * Do not change the value of constants. + * Do not change the size of any of the classes defined in here. + * Do not reference types that are not part of the NDK. + * Do not #include files that aren't part of the NDK. + */ + +#ifndef _NDK_MEDIA_FORMAT_PRIV_H +#define _NDK_MEDIA_FORMAT_PRIV_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +AMediaFormat* AMediaFormat_fromMsg(void*); +void AMediaFormat_getFormat(AMediaFormat* mData, void* dest); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // _NDK_MEDIA_FORMAT_PRIV_H + -- cgit v1.1