MediaMuxer+MediaCodec生成MP4视频报错

来源:互联网 发布:优酷网络剧合作模式 编辑:程序博客网 时间:2024/06/05 06:38

项目中遇到一个问题,现象是录制视频时,录制一段时间后应用会crash。我们项目中是用MediaMuxer+MediaCodec来录制,最后生成一个MP4格式视频。
关键报错信息如下:

E/AndroidRuntime(23507): java.lang.IllegalStateException: writeSampleData returned an errorE/AndroidRuntime(23507):    at android.media.MediaMuxer.nativeWriteSampleData(Native Method)E/AndroidRuntime(23507):    at android.media.MediaMuxer.writeSampleData(MediaMuxer.java:473)

可以看到是native报错,直接去找项目源码(https://android.googlesource.com/platform/frameworks/av/ 这里面可以看到framework的代码):

status_t MediaMuxer::writeSampleData(const sp<ABuffer> &buffer, size_t trackIndex,                                     int64_t timeUs, uint32_t flags) {    Mutex::Autolock autoLock(mMuxerLock);    if (buffer.get() == NULL) {        ALOGE("WriteSampleData() get an NULL buffer.");        return -EINVAL;    }    if (mState != STARTED) {        ALOGE("WriteSampleData() is called in invalid state %d", mState);        return INVALID_OPERATION;    }    if (trackIndex >= mTrackList.size()) {        ALOGE("WriteSampleData() get an invalid index %zu", trackIndex);        return -EINVAL;    }    MediaBuffer* mediaBuffer = new MediaBuffer(buffer);    mediaBuffer->add_ref(); // Released in MediaAdapter::signalBufferReturned().    mediaBuffer->set_range(buffer->offset(), buffer->size());    sp<MetaData> sampleMetaData = mediaBuffer->meta_data();    sampleMetaData->setInt64(kKeyTime, timeUs);    // Just set the kKeyDecodingTime as the presentation time for now.    sampleMetaData->setInt64(kKeyDecodingTime, timeUs);    if (flags & MediaCodec::BUFFER_FLAG_SYNCFRAME) {        sampleMetaData->setInt32(kKeyIsSyncFrame, true);    }    sp<MediaAdapter> currentTrack = mTrackList[trackIndex];    // This pushBuffer will wait until the mediaBuffer is consumed.    return currentTrack->pushBuffer(mediaBuffer);}

发现并没有相应的报错,进一步跟下去跟到MediaAdapter的pushBuffer里面,发现有这一段

 if (!mStarted) {        ALOGE("pushBuffer called before start");        return INVALID_OPERATION; }

果然,在我的crash的log中找到了”pushBuffer called before start”这行log,看来是这个时候已经停掉了。可以为什么会停掉呢?
从这一行log的地方再往上面找,又发现了这样一行log

W/MPEG4Writer(23507): Recorded file size exceeds limit 4294967295bytes

看来很有可能是这个导致的,所以去看MPEG4Writer的代码

if (mOwner->exceedsFileSizeLimit()) {            ALOGW("Recorded file size exceeds limit %" PRId64 "bytes",                    mOwner->mMaxFileSizeLimitBytes);            mOwner->notify(MEDIA_RECORDER_EVENT_INFO, MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED, 0);            break;}

报错的就是这个地方了。看下exceedsFileSizeLimit()这个函数

bool MPEG4Writer::exceedsFileSizeLimit() {    // No limit    if (mMaxFileSizeLimitBytes == 0) {        return false;    }    int64_t nTotalBytesEstimate = static_cast<int64_t>(mEstimatedMoovBoxSize);    for (List<Track *>::iterator it = mTracks.begin();         it != mTracks.end(); ++it) {        nTotalBytesEstimate += (*it)->getEstimatedTrackSizeBytes();    }    if (!mStreamableFile) {        // Add 1024 bytes as error tolerance        return nTotalBytesEstimate + 1024 >= mMaxFileSizeLimitBytes;    }    // Be conservative in the estimate: do not exceed 95% of    // the target file limit. For small target file size limit, though,    // this will not help.    return (nTotalBytesEstimate >= (95 * mMaxFileSizeLimitBytes) / 100);}

可以看到,是否超过大小限制,主要要看mMaxFileSizeLimitBytes这个变量,我们看一下什么地方给它赋了值

    int32_t use64BitOffset;    //----1    if (param &&        param->findInt32(kKey64BitFileOffset, &use64BitOffset) &&        use64BitOffset) {        mUse32BitOffset = false;    }    //----2    if (mUse32BitOffset) {        // Implicit 32 bit file size limit        if (mMaxFileSizeLimitBytes == 0) {            mMaxFileSizeLimitBytes = kMax32BitFileSize;        }        // If file size is set to be larger than the 32 bit file        // size limit, treat it as an error.        if (mMaxFileSizeLimitBytes > kMax32BitFileSize) {            ALOGW("32-bit file size limit (%" PRId64 " bytes) too big. "                 "It is changed to %" PRId64 " bytes",                mMaxFileSizeLimitBytes, kMax32BitFileSize);            mMaxFileSizeLimitBytes = kMax32BitFileSize;        }    }

基本上就是这段逻辑。这个地方要补充说明一个知识点,就是文件系统的格式。有一种格式叫做FAT32,是一种32位的分区格式,Android手机的SD卡基本都是这种格式,这种格式有一个限制,就是单个文件大小最大只能到4G。而现在Android系统大部分都不再使用这种格式了,而是ext格式,是没有这种限制的。所以这个地方我加了注释的1处,就是判断当前是按照32位还是64位来存储,如果是32位才会走注释2的逻辑。而这个变量,搜索一下,发现是在StagefrightRecorder.cpp这个文件中赋值的

(*meta)->setInt32(kKey64BitFileOffset, mUse64BitFileOffset);

再搜索mUse64BitFileOffset这个变量,可以看到默认值是false,也就是32位。赋值的地方有两个

status_t StagefrightRecorder::setParam64BitFileOffset(bool use64Bit) {    ALOGV("setParam64BitFileOffset: %s",        use64Bit? "use 64 bit file offset": "use 32 bit file offset");    mUse64BitFileOffset = use64Bit;    return OK;}status_t StagefrightRecorder::setParamMaxFileSizeBytes(int64_t bytes) {    ALOGV("setParamMaxFileSizeBytes: %" PRId64 " bytes", bytes);    // This is meant for backward compatibility for MediaRecorder.java    if (bytes <= 0) {        ALOGW("Max file size is not positive: %" PRId64 " bytes. "             "Disabling file size limit.", bytes);        bytes = 0; // Disable the file size limit for zero or negative values.    } else if (bytes <= 1024) {  // XXX: 1 kB        ALOGE("Max file size is too small: %" PRId64 " bytes", bytes);        return BAD_VALUE;    }    if (bytes <= 100 * 1024) {        ALOGW("Target file size (%" PRId64 " bytes) is too small to be respected", bytes);    }    mMaxFileSizeBytes = bytes;    // If requested size is >4GB, force 64-bit offsets    mUse64BitFileOffset |= (bytes >= kMax32BitFileSize);    return OK;}

可以看到,setParam64BitFileOffset函数是直接设置是否64位,setParamMaxFileSizeBytes函数是设置最大文件的大小,而当设置的值大于4G的时候,会自动设为64位。这两个函数都是通过setparameter函数来调用的

else if (key == "param-use-64bit-offset") {        int32_t use64BitOffset;        if (safe_strtoi32(value.string(), &use64BitOffset)) {            return setParam64BitFileOffset(use64BitOffset != 0);        }}else if (key == "max-filesize") {        int64_t max_filesize_bytes;        if (safe_strtoi64(value.string(), &max_filesize_bytes)) {            return setParamMaxFileSizeBytes(max_filesize_bytes);        }}

上面那个key我没有搜到,也就是setParam64BitFileOffset这个函数没有调用。下面的key搜索可以搜到在android_media_MediaRecorder.cpp中有调用

static voidandroid_media_MediaRecorder_setMaxFileSize(        JNIEnv *env, jobject thiz, jlong max_filesize_bytes){    ALOGV("setMaxFileSize(%lld)", (long long)max_filesize_bytes);    sp<MediaRecorder> mr = getMediaRecorder(env, thiz);    char params[64];    sprintf(params, "max-filesize=%" PRId64, max_filesize_bytes);    process_media_recorder_call(env, mr->setParameters(String8(params)), "java/lang/RuntimeException", "setMaxFileSize failed.");}

这是一个JNI函数,对应MediaRecorder的setMaxFileSize接口。使用过MediaRecorder的话可能就使用过这个接口,可以设置最大文件size。而MediaRecorder是有一个回调的,回调的位置在一个之前我们看过的地方

if (mOwner->exceedsFileSizeLimit()) {            ALOGW("Recorded file size exceeds limit %" PRId64 "bytes",                    mOwner->mMaxFileSizeLimitBytes);            //callback            mOwner->notify(MEDIA_RECORDER_EVENT_INFO, MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED, 0);            break;}

回头来看,我的项目现在使用的是MediaMuxer+MediaCodec,没有设置这两个参数,也就是应该还是按照32为来走的,也就是上面提到过的注释2的逻辑,那么我们就要看kMax32BitFileSize这个值了。

static const int64_t kMax32BitFileSize = 0x00ffffffffLL; // 2^32-1 : max FAT32 filesystem file size used by most SD cards

可以看到,最大值为4G-1byte,注释也写的很清楚,是为FAT32格式的大部分SD卡所设置的限制。
问题的原因就找到啦~

阅读全文
0 0
原创粉丝点击