| /* |
| * Copyright (C) 2010 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| //#define LOG_NDEBUG 0 |
| #define LOG_TAG "AVCEncoder" |
| #include <utils/Log.h> |
| |
| #include "AVCEncoder.h" |
| |
| #include "avcenc_api.h" |
| #include "avcenc_int.h" |
| #include "OMX_Video.h" |
| |
| #include <media/stagefright/MediaBufferGroup.h> |
| #include <media/stagefright/MediaDebug.h> |
| #include <media/stagefright/MediaDefs.h> |
| #include <media/stagefright/MediaErrors.h> |
| #include <media/stagefright/MetaData.h> |
| #include <media/stagefright/Utils.h> |
| |
| namespace android { |
| |
| static status_t ConvertOmxAvcProfileToAvcSpecProfile( |
| int32_t omxProfile, AVCProfile* pvProfile) { |
| LOGV("ConvertOmxAvcProfileToAvcSpecProfile: %d", omxProfile); |
| switch (omxProfile) { |
| case OMX_VIDEO_AVCProfileBaseline: |
| *pvProfile = AVC_BASELINE; |
| return OK; |
| default: |
| LOGE("Unsupported omx profile: %d", omxProfile); |
| } |
| return BAD_VALUE; |
| } |
| |
| static status_t ConvertOmxAvcLevelToAvcSpecLevel( |
| int32_t omxLevel, AVCLevel *pvLevel) { |
| LOGV("ConvertOmxAvcLevelToAvcSpecLevel: %d", omxLevel); |
| AVCLevel level = AVC_LEVEL5_1; |
| switch (omxLevel) { |
| case OMX_VIDEO_AVCLevel1: |
| level = AVC_LEVEL1_B; |
| break; |
| case OMX_VIDEO_AVCLevel1b: |
| level = AVC_LEVEL1; |
| break; |
| case OMX_VIDEO_AVCLevel11: |
| level = AVC_LEVEL1_1; |
| break; |
| case OMX_VIDEO_AVCLevel12: |
| level = AVC_LEVEL1_2; |
| break; |
| case OMX_VIDEO_AVCLevel13: |
| level = AVC_LEVEL1_3; |
| break; |
| case OMX_VIDEO_AVCLevel2: |
| level = AVC_LEVEL2; |
| break; |
| case OMX_VIDEO_AVCLevel21: |
| level = AVC_LEVEL2_1; |
| break; |
| case OMX_VIDEO_AVCLevel22: |
| level = AVC_LEVEL2_2; |
| break; |
| case OMX_VIDEO_AVCLevel3: |
| level = AVC_LEVEL3; |
| break; |
| case OMX_VIDEO_AVCLevel31: |
| level = AVC_LEVEL3_1; |
| break; |
| case OMX_VIDEO_AVCLevel32: |
| level = AVC_LEVEL3_2; |
| break; |
| case OMX_VIDEO_AVCLevel4: |
| level = AVC_LEVEL4; |
| break; |
| case OMX_VIDEO_AVCLevel41: |
| level = AVC_LEVEL4_1; |
| break; |
| case OMX_VIDEO_AVCLevel42: |
| level = AVC_LEVEL4_2; |
| break; |
| case OMX_VIDEO_AVCLevel5: |
| level = AVC_LEVEL5; |
| break; |
| case OMX_VIDEO_AVCLevel51: |
| level = AVC_LEVEL5_1; |
| break; |
| default: |
| LOGE("Unknown omx level: %d", omxLevel); |
| return BAD_VALUE; |
| } |
| *pvLevel = level; |
| return OK; |
| } |
| |
| inline static void ConvertYUV420SemiPlanarToYUV420Planar( |
| uint8_t *inyuv, uint8_t* outyuv, |
| int32_t width, int32_t height) { |
| |
| int32_t outYsize = width * height; |
| uint32_t *outy = (uint32_t *) outyuv; |
| uint16_t *outcb = (uint16_t *) (outyuv + outYsize); |
| uint16_t *outcr = (uint16_t *) (outyuv + outYsize + (outYsize >> 2)); |
| |
| /* Y copying */ |
| memcpy(outy, inyuv, outYsize); |
| |
| /* U & V copying */ |
| uint32_t *inyuv_4 = (uint32_t *) (inyuv + outYsize); |
| for (int32_t i = height >> 1; i > 0; --i) { |
| for (int32_t j = width >> 2; j > 0; --j) { |
| uint32_t temp = *inyuv_4++; |
| uint32_t tempU = temp & 0xFF; |
| tempU = tempU | ((temp >> 8) & 0xFF00); |
| |
| uint32_t tempV = (temp >> 8) & 0xFF; |
| tempV = tempV | ((temp >> 16) & 0xFF00); |
| |
| // Flip U and V |
| *outcb++ = tempV; |
| *outcr++ = tempU; |
| } |
| } |
| } |
| |
| static int32_t MallocWrapper( |
| void *userData, int32_t size, int32_t attrs) { |
| return reinterpret_cast<int32_t>(malloc(size)); |
| } |
| |
| static void FreeWrapper(void *userData, int32_t ptr) { |
| free(reinterpret_cast<void *>(ptr)); |
| } |
| |
| static int32_t DpbAllocWrapper(void *userData, |
| unsigned int sizeInMbs, unsigned int numBuffers) { |
| AVCEncoder *encoder = static_cast<AVCEncoder *>(userData); |
| CHECK(encoder != NULL); |
| return encoder->allocOutputBuffers(sizeInMbs, numBuffers); |
| } |
| |
| static int32_t BindFrameWrapper( |
| void *userData, int32_t index, uint8_t **yuv) { |
| AVCEncoder *encoder = static_cast<AVCEncoder *>(userData); |
| CHECK(encoder != NULL); |
| return encoder->bindOutputBuffer(index, yuv); |
| } |
| |
| static void UnbindFrameWrapper(void *userData, int32_t index) { |
| AVCEncoder *encoder = static_cast<AVCEncoder *>(userData); |
| CHECK(encoder != NULL); |
| return encoder->unbindOutputBuffer(index); |
| } |
| |
| AVCEncoder::AVCEncoder( |
| const sp<MediaSource>& source, |
| const sp<MetaData>& meta) |
| : mSource(source), |
| mMeta(meta), |
| mNumInputFrames(-1), |
| mPrevTimestampUs(-1), |
| mStarted(false), |
| mInputBuffer(NULL), |
| mInputFrameData(NULL), |
| mGroup(NULL) { |
| |
| LOGI("Construct software AVCEncoder"); |
| |
| mHandle = new tagAVCHandle; |
| memset(mHandle, 0, sizeof(tagAVCHandle)); |
| mHandle->AVCObject = NULL; |
| mHandle->userData = this; |
| mHandle->CBAVC_DPBAlloc = DpbAllocWrapper; |
| mHandle->CBAVC_FrameBind = BindFrameWrapper; |
| mHandle->CBAVC_FrameUnbind = UnbindFrameWrapper; |
| mHandle->CBAVC_Malloc = MallocWrapper; |
| mHandle->CBAVC_Free = FreeWrapper; |
| |
| mInitCheck = initCheck(meta); |
| } |
| |
| AVCEncoder::~AVCEncoder() { |
| LOGV("Destruct software AVCEncoder"); |
| if (mStarted) { |
| stop(); |
| } |
| |
| delete mEncParams; |
| delete mHandle; |
| } |
| |
| status_t AVCEncoder::initCheck(const sp<MetaData>& meta) { |
| LOGV("initCheck"); |
| CHECK(meta->findInt32(kKeyWidth, &mVideoWidth)); |
| CHECK(meta->findInt32(kKeyHeight, &mVideoHeight)); |
| CHECK(meta->findInt32(kKeyFrameRate, &mVideoFrameRate)); |
| CHECK(meta->findInt32(kKeyBitRate, &mVideoBitRate)); |
| |
| // XXX: Add more color format support |
| CHECK(meta->findInt32(kKeyColorFormat, &mVideoColorFormat)); |
| if (mVideoColorFormat != OMX_COLOR_FormatYUV420Planar) { |
| if (mVideoColorFormat != OMX_COLOR_FormatYUV420SemiPlanar) { |
| LOGE("Color format %d is not supported", mVideoColorFormat); |
| return BAD_VALUE; |
| } |
| // Allocate spare buffer only when color conversion is needed. |
| // Assume the color format is OMX_COLOR_FormatYUV420SemiPlanar. |
| mInputFrameData = |
| (uint8_t *) malloc((mVideoWidth * mVideoHeight * 3 ) >> 1); |
| CHECK(mInputFrameData); |
| } |
| |
| // XXX: Remove this restriction |
| if (mVideoWidth % 16 != 0 || mVideoHeight % 16 != 0) { |
| LOGE("Video frame size %dx%d must be a multiple of 16", |
| mVideoWidth, mVideoHeight); |
| return BAD_VALUE; |
| } |
| |
| mEncParams = new tagAVCEncParam; |
| memset(mEncParams, 0, sizeof(mEncParams)); |
| mEncParams->width = mVideoWidth; |
| mEncParams->height = mVideoHeight; |
| mEncParams->frame_rate = 1000 * mVideoFrameRate; // In frames/ms! |
| mEncParams->rate_control = AVC_ON; |
| mEncParams->bitrate = mVideoBitRate; |
| mEncParams->initQP = 0; |
| mEncParams->init_CBP_removal_delay = 1600; |
| mEncParams->CPB_size = (uint32_t) (mVideoBitRate >> 1); |
| |
| mEncParams->intramb_refresh = 0; |
| mEncParams->auto_scd = AVC_ON; |
| mEncParams->out_of_band_param_set = AVC_ON; |
| mEncParams->poc_type = 2; |
| mEncParams->log2_max_poc_lsb_minus_4 = 12; |
| mEncParams->delta_poc_zero_flag = 0; |
| mEncParams->offset_poc_non_ref = 0; |
| mEncParams->offset_top_bottom = 0; |
| mEncParams->num_ref_in_cycle = 0; |
| mEncParams->offset_poc_ref = NULL; |
| |
| mEncParams->num_ref_frame = 1; |
| mEncParams->num_slice_group = 1; |
| mEncParams->fmo_type = 0; |
| |
| mEncParams->db_filter = AVC_ON; |
| mEncParams->disable_db_idc = 0; |
| |
| mEncParams->alpha_offset = 0; |
| mEncParams->beta_offset = 0; |
| mEncParams->constrained_intra_pred = AVC_OFF; |
| |
| mEncParams->data_par = AVC_OFF; |
| mEncParams->fullsearch = AVC_OFF; |
| mEncParams->search_range = 16; |
| mEncParams->sub_pel = AVC_OFF; |
| mEncParams->submb_pred = AVC_OFF; |
| mEncParams->rdopt_mode = AVC_OFF; |
| mEncParams->bidir_pred = AVC_OFF; |
| int32_t nMacroBlocks = ((((mVideoWidth + 15) >> 4) << 4) * |
| (((mVideoHeight + 15) >> 4) << 4)) >> 8; |
| uint32_t *sliceGroup = (uint32_t *) malloc(sizeof(uint32_t) * nMacroBlocks); |
| for (int ii = 0, idx = 0; ii < nMacroBlocks; ++ii) { |
| sliceGroup[ii] = idx++; |
| if (idx >= mEncParams->num_slice_group) { |
| idx = 0; |
| } |
| } |
| mEncParams->slice_group = sliceGroup; |
| |
| mEncParams->use_overrun_buffer = AVC_OFF; |
| |
| // Set IDR frame refresh interval |
| int32_t iFramesIntervalSec; |
| CHECK(meta->findInt32(kKeyIFramesInterval, &iFramesIntervalSec)); |
| if (iFramesIntervalSec < 0) { |
| mEncParams->idr_period = -1; |
| } else if (iFramesIntervalSec == 0) { |
| mEncParams->idr_period = 1; // All I frames |
| } else { |
| mEncParams->idr_period = |
| (iFramesIntervalSec * mVideoFrameRate); |
| } |
| LOGV("idr_period: %d, I-frames interval: %d seconds, and frame rate: %d", |
| mEncParams->idr_period, iFramesIntervalSec, mVideoFrameRate); |
| |
| // Set profile and level |
| // If profile and level setting is not correct, failure |
| // is reported when the encoder is initialized. |
| mEncParams->profile = AVC_BASELINE; |
| mEncParams->level = AVC_LEVEL3_2; |
| int32_t profile, level; |
| if (meta->findInt32(kKeyVideoProfile, &profile)) { |
| if (OK != ConvertOmxAvcProfileToAvcSpecProfile( |
| profile, &mEncParams->profile)) { |
| return BAD_VALUE; |
| } |
| } |
| if (meta->findInt32(kKeyVideoLevel, &level)) { |
| if (OK != ConvertOmxAvcLevelToAvcSpecLevel( |
| level, &mEncParams->level)) { |
| return BAD_VALUE; |
| } |
| } |
| |
| |
| mFormat = new MetaData; |
| mFormat->setInt32(kKeyWidth, mVideoWidth); |
| mFormat->setInt32(kKeyHeight, mVideoHeight); |
| mFormat->setInt32(kKeyBitRate, mVideoBitRate); |
| mFormat->setInt32(kKeyFrameRate, mVideoFrameRate); |
| mFormat->setInt32(kKeyColorFormat, mVideoColorFormat); |
| mFormat->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_AVC); |
| mFormat->setCString(kKeyDecoderComponent, "AVCEncoder"); |
| return OK; |
| } |
| |
| status_t AVCEncoder::start(MetaData *params) { |
| LOGV("start"); |
| if (mInitCheck != OK) { |
| return mInitCheck; |
| } |
| |
| if (mStarted) { |
| LOGW("Call start() when encoder already started"); |
| return OK; |
| } |
| |
| AVCEnc_Status err; |
| err = PVAVCEncInitialize(mHandle, mEncParams, NULL, NULL); |
| if (err != AVCENC_SUCCESS) { |
| LOGE("Failed to initialize the encoder: %d", err); |
| return UNKNOWN_ERROR; |
| } |
| |
| mGroup = new MediaBufferGroup(); |
| int32_t maxSize; |
| if (AVCENC_SUCCESS != |
| PVAVCEncGetMaxOutputBufferSize(mHandle, &maxSize)) { |
| maxSize = 31584; // Magic # |
| } |
| mGroup->add_buffer(new MediaBuffer(maxSize)); |
| |
| mSource->start(params); |
| mNumInputFrames = -2; // 1st two buffers contain SPS and PPS |
| mStarted = true; |
| mSpsPpsHeaderReceived = false; |
| mReadyForNextFrame = true; |
| mIsIDRFrame = 0; |
| |
| return OK; |
| } |
| |
| status_t AVCEncoder::stop() { |
| LOGV("stop"); |
| if (!mStarted) { |
| LOGW("Call stop() when encoder has not started"); |
| return OK; |
| } |
| |
| if (mInputBuffer) { |
| mInputBuffer->release(); |
| mInputBuffer = NULL; |
| } |
| |
| if (mGroup) { |
| delete mGroup; |
| mGroup = NULL; |
| } |
| |
| if (mInputFrameData) { |
| delete mInputFrameData; |
| mInputFrameData = NULL; |
| } |
| |
| PVAVCCleanUpEncoder(mHandle); |
| mSource->stop(); |
| releaseOutputBuffers(); |
| mStarted = false; |
| |
| return OK; |
| } |
| |
| void AVCEncoder::releaseOutputBuffers() { |
| LOGV("releaseOutputBuffers"); |
| for (size_t i = 0; i < mOutputBuffers.size(); ++i) { |
| MediaBuffer *buffer = mOutputBuffers.editItemAt(i); |
| buffer->setObserver(NULL); |
| buffer->release(); |
| } |
| mOutputBuffers.clear(); |
| } |
| |
| sp<MetaData> AVCEncoder::getFormat() { |
| LOGV("getFormat"); |
| return mFormat; |
| } |
| |
| status_t AVCEncoder::read( |
| MediaBuffer **out, const ReadOptions *options) { |
| |
| CHECK(!options); |
| *out = NULL; |
| |
| MediaBuffer *outputBuffer; |
| CHECK_EQ(OK, mGroup->acquire_buffer(&outputBuffer)); |
| uint8_t *outPtr = (uint8_t *) outputBuffer->data(); |
| uint32_t dataLength = outputBuffer->size(); |
| |
| if (!mSpsPpsHeaderReceived && mNumInputFrames < 0) { |
| // 4 bytes are reserved for holding the start code 0x00000001 |
| // of the sequence parameter set at the beginning. |
| outPtr += 4; |
| dataLength -= 4; |
| } |
| |
| int32_t type; |
| AVCEnc_Status encoderStatus = AVCENC_SUCCESS; |
| |
| // Combine SPS and PPS and place them in the very first output buffer |
| // SPS and PPS are separated by start code 0x00000001 |
| // Assume that we have exactly one SPS and exactly one PPS. |
| while (!mSpsPpsHeaderReceived && mNumInputFrames <= 0) { |
| encoderStatus = PVAVCEncodeNAL(mHandle, outPtr, &dataLength, &type); |
| if (encoderStatus == AVCENC_WRONG_STATE) { |
| mSpsPpsHeaderReceived = true; |
| CHECK_EQ(0, mNumInputFrames); // 1st video frame is 0 |
| } else { |
| switch (type) { |
| case AVC_NALTYPE_SPS: |
| ++mNumInputFrames; |
| memcpy((uint8_t *)outputBuffer->data(), "\x00\x00\x00\x01", 4); |
| outputBuffer->set_range(0, dataLength + 4); |
| outPtr += (dataLength + 4); // 4 bytes for next start code |
| dataLength = outputBuffer->size() - |
| (outputBuffer->range_length() + 4); |
| break; |
| case AVC_NALTYPE_PPS: |
| ++mNumInputFrames; |
| memcpy(((uint8_t *) outputBuffer->data()) + |
| outputBuffer->range_length(), |
| "\x00\x00\x00\x01", 4); |
| outputBuffer->set_range(0, |
| dataLength + outputBuffer->range_length() + 4); |
| outputBuffer->meta_data()->setInt32(kKeyIsCodecConfig, 1); |
| outputBuffer->meta_data()->setInt64(kKeyTime, 0); |
| *out = outputBuffer; |
| return OK; |
| default: |
| LOGE("Nal type (%d) other than SPS/PPS is unexpected", type); |
| return UNKNOWN_ERROR; |
| } |
| } |
| } |
| |
| // Get next input video frame |
| if (mReadyForNextFrame) { |
| if (mInputBuffer) { |
| mInputBuffer->release(); |
| mInputBuffer = NULL; |
| } |
| status_t err = mSource->read(&mInputBuffer, options); |
| if (err != OK) { |
| LOGE("Failed to read input video frame: %d", err); |
| outputBuffer->release(); |
| return err; |
| } |
| |
| if (mInputBuffer->size() - ((mVideoWidth * mVideoHeight * 3) >> 1) != 0) { |
| outputBuffer->release(); |
| mInputBuffer->release(); |
| mInputBuffer = NULL; |
| return UNKNOWN_ERROR; |
| } |
| |
| int64_t timeUs; |
| CHECK(mInputBuffer->meta_data()->findInt64(kKeyTime, &timeUs)); |
| outputBuffer->meta_data()->setInt64(kKeyTime, timeUs); |
| |
| // When the timestamp of the current sample is the same as |
| // that of the previous sample, the encoding of the sample |
| // is bypassed, and the output length is set to 0. |
| if (mNumInputFrames >= 1 && mPrevTimestampUs == timeUs) { |
| // Frame arrives too late |
| mInputBuffer->release(); |
| mInputBuffer = NULL; |
| outputBuffer->set_range(0, 0); |
| *out = outputBuffer; |
| return OK; |
| } |
| |
| // Don't accept out-of-order samples |
| CHECK(mPrevTimestampUs < timeUs); |
| mPrevTimestampUs = timeUs; |
| |
| AVCFrameIO videoInput; |
| memset(&videoInput, 0, sizeof(videoInput)); |
| videoInput.height = ((mVideoHeight + 15) >> 4) << 4; |
| videoInput.pitch = ((mVideoWidth + 15) >> 4) << 4; |
| videoInput.coding_timestamp = (timeUs + 500) / 1000; // in ms |
| uint8_t *inputData = (uint8_t *) mInputBuffer->data(); |
| |
| if (mVideoColorFormat != OMX_COLOR_FormatYUV420Planar) { |
| CHECK(mInputFrameData); |
| CHECK(mVideoColorFormat == OMX_COLOR_FormatYUV420SemiPlanar); |
| ConvertYUV420SemiPlanarToYUV420Planar( |
| inputData, mInputFrameData, mVideoWidth, mVideoHeight); |
| inputData = mInputFrameData; |
| } |
| CHECK(inputData != NULL); |
| videoInput.YCbCr[0] = inputData; |
| videoInput.YCbCr[1] = videoInput.YCbCr[0] + videoInput.height * videoInput.pitch; |
| videoInput.YCbCr[2] = videoInput.YCbCr[1] + |
| ((videoInput.height * videoInput.pitch) >> 2); |
| videoInput.disp_order = mNumInputFrames; |
| |
| encoderStatus = PVAVCEncSetInput(mHandle, &videoInput); |
| if (encoderStatus == AVCENC_SUCCESS || |
| encoderStatus == AVCENC_NEW_IDR) { |
| mReadyForNextFrame = false; |
| ++mNumInputFrames; |
| if (encoderStatus == AVCENC_NEW_IDR) { |
| mIsIDRFrame = 1; |
| } |
| } else { |
| if (encoderStatus < AVCENC_SUCCESS) { |
| outputBuffer->release(); |
| return UNKNOWN_ERROR; |
| } else { |
| outputBuffer->set_range(0, 0); |
| *out = outputBuffer; |
| return OK; |
| } |
| } |
| } |
| |
| // Encode an input video frame |
| CHECK(encoderStatus == AVCENC_SUCCESS || |
| encoderStatus == AVCENC_NEW_IDR); |
| dataLength = outputBuffer->size(); // Reset the output buffer length |
| encoderStatus = PVAVCEncodeNAL(mHandle, outPtr, &dataLength, &type); |
| if (encoderStatus == AVCENC_SUCCESS) { |
| outputBuffer->meta_data()->setInt32(kKeyIsSyncFrame, mIsIDRFrame); |
| CHECK_EQ(NULL, PVAVCEncGetOverrunBuffer(mHandle)); |
| } else if (encoderStatus == AVCENC_PICTURE_READY) { |
| CHECK_EQ(NULL, PVAVCEncGetOverrunBuffer(mHandle)); |
| if (mIsIDRFrame) { |
| outputBuffer->meta_data()->setInt32(kKeyIsSyncFrame, mIsIDRFrame); |
| mIsIDRFrame = 0; |
| LOGV("Output an IDR frame"); |
| } |
| mReadyForNextFrame = true; |
| AVCFrameIO recon; |
| if (PVAVCEncGetRecon(mHandle, &recon) == AVCENC_SUCCESS) { |
| PVAVCEncReleaseRecon(mHandle, &recon); |
| } |
| } else { |
| dataLength = 0; |
| mReadyForNextFrame = true; |
| } |
| if (encoderStatus < AVCENC_SUCCESS) { |
| outputBuffer->release(); |
| return UNKNOWN_ERROR; |
| } |
| |
| outputBuffer->set_range(0, dataLength); |
| *out = outputBuffer; |
| return OK; |
| } |
| |
| int32_t AVCEncoder::allocOutputBuffers( |
| unsigned int sizeInMbs, unsigned int numBuffers) { |
| CHECK(mOutputBuffers.isEmpty()); |
| size_t frameSize = (sizeInMbs << 7) * 3; |
| for (unsigned int i = 0; i < numBuffers; ++i) { |
| MediaBuffer *buffer = new MediaBuffer(frameSize); |
| buffer->setObserver(this); |
| mOutputBuffers.push(buffer); |
| } |
| |
| return 1; |
| } |
| |
| void AVCEncoder::unbindOutputBuffer(int32_t index) { |
| CHECK(index >= 0); |
| } |
| |
| int32_t AVCEncoder::bindOutputBuffer(int32_t index, uint8_t **yuv) { |
| CHECK(index >= 0); |
| CHECK(index < (int32_t) mOutputBuffers.size()); |
| int64_t timeUs; |
| CHECK(mInputBuffer->meta_data()->findInt64(kKeyTime, &timeUs)); |
| mOutputBuffers[index]->meta_data()->setInt64(kKeyTime, timeUs); |
| |
| *yuv = (uint8_t *) mOutputBuffers[index]->data(); |
| |
| return 1; |
| } |
| |
| void AVCEncoder::signalBufferReturned(MediaBuffer *buffer) { |
| } |
| |
| } // namespace android |