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卡所设置的限制。
问题的原因就找到啦~
- MediaMuxer+MediaCodec生成MP4视频报错
- Android在MediaMuxer和MediaCodec录制视频示例 - audio+video
- Android-->MediaMuxer,MediaCodec,AudioRecord及Camera实现音频视频混合MP4文件
- 【转载】Android-->MediaMuxer,MediaCodec,AudioRecord及Camera实现音频视频混合MP4文件
- Android-->MediaMuxer,MediaCodec,AudioRecord及Camera实现音频视频混合MP4文件
- ffmpeg开发之旅(3):AAC编码格式分析与MP4文件封装(MediaCodec+MediaMuxer)
- ffmpeg开发之旅(3):AAC编码格式分析与MP4文件封装(MediaCodec+MediaMuxer)
- Android音视频处理之MediaMuxer(MP4)
- Android中如何提取和生成mp4文件(MediaMUxer)
- Anroid中MediaMuxer,MediaCodec,MediaExtractor用例
- Android中MediaCodec和MediaMuxer的使用
- Android中MediaMuxer和MediaCodec用例
- MediaMuxer实现音视频分离
- Android中MediaMuxer和MediaCodec用例 - audio+video
- Android中MediaMuxer和MediaCodec用例 - audio+video
- Android中MediaMuxer和MediaCodec用例 - audio+video
- Android中MediaMuxer和MediaCodec用例 - audio+video
- Android中MediaMuxer和MediaCodec用例 - audio+video
- HashMap 、 ArrayList、String 重写了equals方法 而Object类(比如User)没有重写
- 设计模式-----------单例模式
- socket的基本函数
- 查看java内存情况命令
- Oracle中rownum、rowid、row_number()、rank()、dense_rank()的区别
- MediaMuxer+MediaCodec生成MP4视频报错
- 明星之间的尴尬同框,最萌身高差,最后一个笑喷
- 熔断器使用
- 互联网协议入门(一)
- maven入门:依赖 聚合 继承(一)
- 从0开始做安卓/IOS拍照摄像上传APP【app+后台】【工具 eclipse+Hbuilder】
- dp——洛谷 P1435 回文字串
- jquery alert提示框自动消失
- 开源项目 无限循环ViewPager InfiniteViewPager 分析(二)