前面已经分析过 MediaRecorder 初始化和配置过程,接下来就可以真正进入录制流程了。现在不难得出这个结论:MediaRecorder 录制 Camera 的数据实际上是将预览数据经过 MediaCodec 硬编码后封装成对应的容器。具体到现在谈的场景实际上将渲染数据直接绘制到硬编码器 Surface 上然后去编码,编码后的数据是 H264,将 MIC 音频硬编码为 AAC,然后将它们封装成 MP4。
开始捕获数据并将数据编码到使用 setOutputFile() 指定的文件中。在 prepare() 之后调用它。此方法实现在 JNI 层。
frameworks/base/media/java/android/media/MediaRecorder.java
public class MediaRecorder implements AudioRouting,
AudioRecordingMonitor,
AudioRecordingMonitorClient,
MicrophoneDirection
{
......
public native void start() throws IllegalStateException;
......
}
- 调用 getMediaRecorder(…) 获取 MediaRecorder native 对象;
- 调用 MediaRecorder native 对象的 start() 方法进一步处理开启流程。
frameworks/base/media/jni/android_media_MediaRecorder.cpp
android_media_MediaRecorder_start(JNIEnv *env, jobject thiz)
{
ALOGV("start");
sp<MediaRecorder> mr = getMediaRecorder(env, thiz);
if (mr == NULL) {
jniThrowException(env, "java/lang/IllegalStateException", NULL);
return;
}
process_media_recorder_call(env, mr->start(), "java/lang/RuntimeException", "start failed.");
}
- 检查 mMediaRecorder 是否为 NULL,它是 MediaRecorderClient 对应实现的 Binder 代理端;
- 检查当前状态是否为 MEDIA_RECORDER_PREPARED,也就是说如果没有准备就绪就去开启直接打印 err Log,并直接返回 INVALID_OPERATION;
- 调用 MediaRecorderClient start() 进一步去开启录制;
- 最后将 mCurrentState 状态更新为 MEDIA_RECORDER_RECORDING(录制中)。
frameworks/av/media/libmedia/mediarecorder.cpp
status_t MediaRecorder::start()
{
ALOGV("start");
if (mMediaRecorder == NULL) {
ALOGE("media recorder is not initialized yet");
return INVALID_OPERATION;
}
if (!(mCurrentState & MEDIA_RECORDER_PREPARED)) {
ALOGE("start called in an invalid state: %d", mCurrentState);
return INVALID_OPERATION;
}
status_t ret = mMediaRecorder->start();
if (OK != ret) {
ALOGE("start failed: %d", ret);
mCurrentState = MEDIA_RECORDER_ERROR;
return ret;
}
mCurrentState = MEDIA_RECORDER_RECORDING;
return ret;
}
MediaRecorderClient::start() 方法仅仅立马调用了 StagefrightRecorder::start() 进行处理。
frameworks/av/media/libmediaplayerservice/MediaRecorderClient.cpp
status_t MediaRecorderClient::start()
{
ALOGV("start");
Mutex::Autolock lock(mLock);
if (mRecorder == NULL) {
ALOGE("recorder is not initialized");
return NO_INIT;
}
return mRecorder->start();
}
- 判断输出文件描述符是否有效;
- 判断视频源是否为 VIDEO_SOURCE_SURFACE,此场景下这个 if 结构显然不会进入;
- 判断 mWriter 是否为空,这里其被赋值为指向 MPEG4Writer 对象;
- 由于录制格式被设置为 MP4,因此 isMPEG4 = true,接着调用 setupMPEG4orWEBMMetaData(…) 设置元数据,最后调用 MPEG4Writer 对象 start(…) 方法进一步处理;
- 如果 MPEG4Writer 对象 start(…) 后返回状态非 OK,说明期间出现了问题,将 mWriter 这个强指针清空;
- 如果 MPEG4Writer 对象 start(…) 后返回 OK,并且 mStarted(表示是否真的开始了硬编码) 为 false,首先将 mStarted 置为 true,mStartedRecordingUs(开始录像时间)赋值为当前系统时间,最后调用 addBatteryData(…) 跟踪录制期间电池数据。
frameworks/av/media/libmediaplayerservice/StagefrightRecorder.cpp
status_t StagefrightRecorder::start() {
ALOGV("start");
Mutex::Autolock autolock(mLock);
if (mOutputFd < 0) {
ALOGE("Output file descriptor is invalid");
return INVALID_OPERATION;
}
status_t status = OK;
if (mVideoSource != VIDEO_SOURCE_SURFACE) {
status = prepareInternal();
if (status != OK) {
return status;
}
}
if (mWriter == NULL) {
ALOGE("File writer is not avaialble");
return UNKNOWN_ERROR;
}
switch (mOutputFormat) {
case OUTPUT_FORMAT_DEFAULT:
case OUTPUT_FORMAT_THREE_GPP:
case OUTPUT_FORMAT_MPEG_4:
case OUTPUT_FORMAT_WEBM:
{
bool isMPEG4 = true;
if (mOutputFormat == OUTPUT_FORMAT_WEBM) {
isMPEG4 = false;
}
sp<MetaData> meta = new MetaData;
setupMPEG4orWEBMMetaData(&meta);
status = mWriter->start(meta.get());
break;
}
case OUTPUT_FORMAT_AMR_NB:
case OUTPUT_FORMAT_AMR_WB:
case OUTPUT_FORMAT_AAC_ADIF:
case OUTPUT_FORMAT_AAC_ADTS:
case OUTPUT_FORMAT_RTP_AVP:
case OUTPUT_FORMAT_MPEG2TS:
case OUTPUT_FORMAT_OGG:
{
sp<MetaData> meta = new MetaData;
int64_t startTimeUs = systemTime() / 1000;
meta->setInt64(kKeyTime, startTimeUs);
status = mWriter->start(meta.get());
break;
}
default:
{
ALOGE("Unsupported output file format: %d", mOutputFormat);
status = UNKNOWN_ERROR;
break;
}
}
if (status != OK) {
mWriter.clear();
mWriter = NULL;
}
if ((status == OK) && (!mStarted)) {
mAnalyticsDirty = true;
mStarted = true;
mStartedRecordingUs = systemTime() / 1000;
uint32_t params = IMediaPlayerService::kBatteryDataCodecStarted;
if (mAudioSource != AUDIO_SOURCE_CNT) {
params |= IMediaPlayerService::kBatteryDataTrackAudio;
}
if (mVideoSource != VIDEO_SOURCE_LIST_END) {
params |= IMediaPlayerService::kBatteryDataTrackVideo;
}
addBatteryData(params);
}
return status;
}
setupMPEG4orWEBMMetaData(…) 方法内做了开始时间、文件类型、比特率、64位文件偏移、轨迹延时和旋转角度设置。
frameworks/av/media/libmediaplayerservice/StagefrightRecorder.cpp
void StagefrightRecorder::setupMPEG4orWEBMMetaData(sp<MetaData> *meta) {
int64_t startTimeUs = systemTime() / 1000;
(*meta)->setInt64(kKeyTime, startTimeUs);
(*meta)->setInt32(kKeyFileType, mOutputFormat);
(*meta)->setInt32(kKeyBitRate, mTotalBitRate);
if (mMovieTimeScale > 0) {
(*meta)->setInt32(kKeyTimeScale, mMovieTimeScale);
}
if (mOutputFormat != OUTPUT_FORMAT_WEBM) {
(*meta)->setInt32(kKey64BitFileOffset, mUse64BitFileOffset);
if (mTrackEveryTimeDurationUs > 0) {
(*meta)->setInt64(kKeyTrackTimeStatus, mTrackEveryTimeDurationUs);
}
if (mRotationDegrees != 0) {
(*meta)->setInt32(kKeyRotation, mRotationDegrees);
}
}
}
MPEG4Writer::start(…) 大致调用流程如下:
-
检查最大文件大小限制(mMaxFileSizeLimitBytes)是否为 0,如果不为 0,则修改 mIsFileSizeLimitExplicitlyRequested 布尔值为 true。
-
检查参数设置中是否开启 64 位文件偏移量,如果开启就将 mUse32BitOffset 置为 false,表征使用 32 位文件偏移量。
-
如果使用 32 位文件偏移量,在没设置 mMaxFileSizeLimitBytes 的时候,把文件大小默认限制为 kMax32BitFileSize(0x00ffffffffLL 2^32-1 : max FAT32);如果设置的最大文件大小限制大于 32 位限制,则将限制也设置为 kMax32BitFileSize 大小 。
-
检查参数设置中是否使用 2 个字节的 NAL 长度,如果设置了就将 mUse4ByteNalLength 置为 false,也就是不使用4 个字节的 NAL 长度,如果未设置则使用 4 个字节的 NAL 长度。
-
检查参数设置中是否开启实时录制,如果开启就将 mIsRealTimeRecording 置为相应的参数值。
-
如果调用 MPEG4Writer::start(…) 时,已经开启了录制则 mStarted 肯定为 true,并且在 mPaused 为 true,也就是暂停状态,则调用 startTracks(…) 并直接返回。
-
检查参数设置中是否配置了时间缩放比,如果没配置时间缩放比(mTimeScale)则设置为 1000。
-
当请求的文件大小限制较小时,优先级是满足文件大小限制要求,而不是使文件可流化。mStreamableFile 不能代表实际记录的文件是否可流化。
-
如果文件级 meta(元数据) 或 moov box 中的数据量小于文件开头的预留空闲空间,并且当 box 的内容被构造时,mWriteBoxToMemory 为 true。请注意,视频/音频帧数据总是写入文件,而不是在内存中。在调用 stop()/reset() 之前,mWriteBoxToMemory 总是 false。当 reset() 在录制会话结束时被调用,文件级元和/或 moov box 需要构造。
9.1 在 box 构造之前,将 mWriteBoxToMemory 设置为 mStreamableFile,以便如果文件是可流化的,则设置为 true;否则,它被设置为 false。当该值设置为 false 时,该 box 的所有内容将立即写入文件的末尾。当该值设置为 true 时,该 box 的所有内容都被写入内存中的缓存 mInMemoryCache,直到下列条件发生。注意,内存中缓存的大小与文件开头保留的可用空间相同。
9.2 当 box 的数据被写入内存缓存时,数据大小会根据预留的空间进行检查。如果数据的大小超过了预留的空间,后续的 box 数据将不能再保存在内存缓存中。这也表明预留空间太小。此时,必须将 all 后续 box 数据写到文件的末尾。mWriteBoxToMemory 必须设置为 false 才能将写操作定向到文件。
9.3 当 box 构建完成后,如果 box 内的数据小于预留空间,则将 box 的内存缓存副本写入预留的可用空间。在所有使用内存缓存的 box 被构造之后,mWriteBoxToMemory 总是被设置为 false。
-
调用 writeFtypBox(…) 写 ftyp box(ftyp box 有且只有一个,在 mp4 文件最开始的地方,并且只能被包含在文件层,而不能被其它 box 包含。它依次包括 1 个 32 位的 major brand(4个字符),1 个 32 位的 minor version(整数)和 1 个以 32 位(4个字符)为单位元素的数组 compatible brands。这些都是用来指示文件应用级别的信息)。
-
mInMemoryCacheSize 等于 0 说明内容暂时没有在内存缓存中,如果存在文件级元信息,就调用 estimateFileLevelMetaSize(…) 去估计文件级别元数据大小,并将返回值赋给 mInMemoryCacheSize。如果存在 moov box(moov box 是一个 container box,该 box 包含了文件媒体的元数据信息,具体内容信息由子 box 诠释。同 File Type Box 一样,该 box 有且只有一个,且只被包含在文件层。一般情况下,“moov”会紧随“ftyp”出现),并且参数中也配置了比特率,就将比特率作为入参(参数中没配置默认为 -1)调用 estimateMoovBoxSize(…) 去估计 moov box 的大小,返回值加到 mInMemoryCacheSize 上。
-
如果 mStreamableFile 为 true,为可流化文件保留一个“free” box。根据给 free box 保留的大小和 mInMemoryCacheSize 计算 mdat box(Meida Data Box 媒体数据 box 位于顶层,定义是一个字节数组,用来存储媒体数据)的文件偏移量,并根据 mUse32BitOffset 是否为 true 决定写入 ???mdat 或 \x00\x00\x00\x01mdat??? 字串到文件。
-
接下来调用 startWriterThread() 开启写线程。
-
最后调用 startTracks(…) 开始真正处理轨迹相关实际音轨、视频轨迹数据。最后一步 mStarted 置为 true,表明录像已开始。
frameworks/av/media/libstagefright/MPEG4Writer.cpp
status_t MPEG4Writer::start(MetaData *param) {
if (mInitCheck != OK) {
return UNKNOWN_ERROR;
}
mStartMeta = param;
/*
* Check mMaxFileSizeLimitBytes at the beginning
* since mMaxFileSizeLimitBytes may be implicitly
* changed later for 32-bit file offset even if
* user does not ask to set it explicitly.
*/
if (mMaxFileSizeLimitBytes != 0) {
mIsFileSizeLimitExplicitlyRequested = true;
}
int32_t use64BitOffset;
if (param &&
param->findInt32(kKey64BitFileOffset, &use64BitOffset) &&
use64BitOffset) {
mUse32BitOffset = false;
}
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;
}
}
int32_t use2ByteNalLength;
if (param &&
param->findInt32(kKey2ByteNalLength, &use2ByteNalLength) &&
use2ByteNalLength) {
mUse4ByteNalLength = false;
}
int32_t isRealTimeRecording;
if (param && param->findInt32(kKeyRealTimeRecording, &isRealTimeRecording)) {
mIsRealTimeRecording = isRealTimeRecording;
}
mStartTimestampUs = -1;
if (mStarted) {
if (mPaused) {
mPaused = false;
return startTracks(param);
}
return OK;
}
if (!param ||
!param->findInt32(kKeyTimeScale, &mTimeScale)) {
mTimeScale = 1000;
}
CHECK_GT(mTimeScale, 0);
ALOGV("movie time scale: %d", mTimeScale);
......
mWriteBoxToMemory = false;
mInMemoryCache = NULL;
mInMemoryCacheOffset = 0;
ALOGV("muxer starting: mHasMoovBox %d, mHasFileLevelMeta %d",
mHasMoovBox, mHasFileLevelMeta);
writeFtypBox(param);
mFreeBoxOffset = mOffset;
if (mInMemoryCacheSize == 0) {
int32_t bitRate = -1;
if (mHasFileLevelMeta) {
mInMemoryCacheSize += estimateFileLevelMetaSize(param);
}
if (mHasMoovBox) {
if (param) {
param->findInt32(kKeyBitRate, &bitRate);
}
mInMemoryCacheSize += estimateMoovBoxSize(bitRate);
}
}
if (mStreamableFile) {
// Reserve a 'free' box only for streamable file
lseek64(mFd, mFreeBoxOffset, SEEK_SET);
writeInt32(mInMemoryCacheSize);
write("free", 4);
mMdatOffset = mFreeBoxOffset + mInMemoryCacheSize;
} else {
mMdatOffset = mOffset;
}
mOffset = mMdatOffset;
lseek64(mFd, mMdatOffset, SEEK_SET);
if (mUse32BitOffset) {
write("????mdat", 8);
} else {
write("\x00\x00\x00\x01mdat????????", 16);
}
status_t err = startWriterThread();
if (err != OK) {
return err;
}
err = startTracks(param);
if (err != OK) {
return err;
}
mStarted = true;
return OK;
}
- 遍历 mTracks 引用的 List<Track *>,将每个轨迹等写入 ChunkInfo(区块信息),然后将这个 ChunkInfo 写到 mChunkInfos 引用的 List;
- 调用 pthread_create(…) 启动线程,它会执行 MPEG4Writer::ThreadWrapper(…) 函数,并且给这个线程设置了 detach 状态为 PTHREAD_CREATE_JOINABLE(可以 join)。最后将 mWriterThreadStarted 置为 true,表征写者线程已开启。
frameworks/av/media/libstagefright/MPEG4Writer.cpp
status_t MPEG4Writer::startWriterThread() {
ALOGV("startWriterThread");
mDone = false;
mIsFirstChunk = true;
mDriftTimeUs = 0;
for (List<Track *>::iterator it = mTracks.begin();
it != mTracks.end(); ++it) {
ChunkInfo info;
info.mTrack = *it;
info.mPrevChunkTimestampUs = 0;
info.mMaxInterChunkDurUs = 0;
mChunkInfos.push_back(info);
}
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
pthread_create(&mThread, &attr, ThreadWrapper, this);
pthread_attr_destroy(&attr);
mWriterThreadStarted = true;
return OK;
}
MPEG4Writer::ThreadWrapper(…) 只是简单的调用了 MPEG4Writer 对象的 threadFunc() 方法,这是一种使用 pthread 线程的常见用法,目的在于 pthread 线程调用类内部的函数。
frameworks/av/media/libstagefright/MPEG4Writer.cpp
// static
void *MPEG4Writer::ThreadWrapper(void *me) {
ALOGV("ThreadWrapper: %p", me);
MPEG4Writer *writer = static_cast<MPEG4Writer *>(me);
writer->threadFunc();
return NULL;
}
- 调用 prctl(…) 为线程设置线程名称为 MPEG4Writer;
- mDone 开始录制时为 false,正常情况下只有结束录制才会置为 true,因此第二个 while 循环中 findChunkToWrite(…) 当找不到 Chunk(区块),就会在条件变量 mChunkReadyCondition 上等待。找到了继续向下执行代码调用 writeChunkToFile(…) 将找到的区块写入文件。之所以写入的时候取消锁,这个是为了性能的考量(在实时录制模式下,在不持有锁的情况下写,以减少媒体 track 线程的阻塞时间。否则保持锁,直到现有的数据块被写入文件)。
- mDone 被置为了 false,已经停止录制,调用 writeAllChunks() 写入剩余的全部区块。
frameworks/av/media/libstagefright/MPEG4Writer.cpp
void MPEG4Writer::threadFunc() {
ALOGV("threadFunc");
prctl(PR_SET_NAME, (unsigned long)"MPEG4Writer", 0, 0, 0);
Mutex::Autolock autoLock(mLock);
while (!mDone) {
Chunk chunk;
bool chunkFound = false;
while (!mDone && !(chunkFound = findChunkToWrite(&chunk))) {
mChunkReadyCondition.wait(mLock);
}
// In real time recording mode, write without holding the lock in order
// to reduce the blocking time for media track threads.
// Otherwise, hold the lock until the existing chunks get written to the
// file.
if (chunkFound) {
if (mIsRealTimeRecording) {
mLock.unlock();
}
writeChunkToFile(&chunk);
if (mIsRealTimeRecording) {
mLock.lock();
}
}
}
writeAllChunks();
}
findChunkToWrite(…) 从名字不难看出它用来查找可写入的区块。
- 遍历 mChunkInfos(List)列表,找到这些 ChunkInfo 结构中 mChunks(剩下的要写的块)中首个 Chunk 中时间戳最小的 Chunk 对应的 Track。
- 如果没有找到这样的轨迹,打印 log:Nothing to be written after all,并返回 false。
- 如果 mIsFirstChunk(目前这个标志看起来没什么用) 为 true,也就是首个区块,先将 mIsFirstChunk 置为 false。
- 再次遍历 mChunkInfos(List)列表,找到第一步遍历找到的 Track 对应的 ChunkInfo,然后拿到其内部的 mChunk 列表,然后将第一个 Chunk 取出来,接着将其从列表中擦除。最后根据拿到区块的时间戳和轨迹中记录的已写入的前一个块时间戳的差值,决定是否更新轨迹内的 mMaxInterChunkDurUs(相邻块之间的最大时间间隔)。
frameworks/av/media/libstagefright/MPEG4Writer.cpp
bool MPEG4Writer::findChunkToWrite(Chunk *chunk) {
ALOGV("findChunkToWrite");
int64_t minTimestampUs = 0x7FFFFFFFFFFFFFFFLL;
Track *track = NULL;
for (List<ChunkInfo>::iterator it = mChunkInfos.begin();
it != mChunkInfos.end(); ++it) {
if (!it->mChunks.empty()) {
List<Chunk>::iterator chunkIt = it->mChunks.begin();
if (chunkIt->mTimeStampUs < minTimestampUs) {
minTimestampUs = chunkIt->mTimeStampUs;
track = it->mTrack;
}
}
}
if (track == NULL) {
ALOGV("Nothing to be written after all");
return false;
}
if (mIsFirstChunk) {
mIsFirstChunk = false;
}
for (List<ChunkInfo>::iterator it = mChunkInfos.begin();
it != mChunkInfos.end(); ++it) {
if (it->mTrack == track) {
*chunk = *(it->mChunks.begin());
it->mChunks.erase(it->mChunks.begin());
CHECK_EQ(chunk->mTrack, track);
int64_t interChunkTimeUs =
chunk->mTimeStampUs - it->mPrevChunkTimestampUs;
if (interChunkTimeUs > it->mPrevChunkTimestampUs) {
it->mMaxInterChunkDurUs = interChunkTimeUs;
}
return true;
}
}
return false;
}
writeChunkToFile(…) 主要作用是将块写入文件。
- 区块中样本数据非空,就会遍历所有的样本列表(List<MediaBuffer *>)。
- 查找 MediaBuffer 中是否包含指定偏移量 tiff HDR 的 exif 数据块,如果包含 tiffHdrOffset 大于 0,isExif 被赋值为 true。由于我们录制的是标准 MP4,因此 usePrefix() 中 mIsAvc 就为 true,也就是 usePrefix() 返回 true。usePrefix 的值主要取决于 isExif 这个布尔值。
- 调用 addSample_l(…) 真正去写样本数据到文件。
- 由于 isFirstSample 默认为 true,标准 MP4 录制非 heic,所以就会调用到 Track::addChunkOffset(…) 添加块偏移量。
- MediaBuffer 使用完后调用 release() 进行释放,并将其从样本列表中擦除。
- 全部样本被写完后,对样本列表 clear() 一下保证其清空。
frameworks/av/media/libstagefright/MPEG4Writer.cpp
void MPEG4Writer::writeChunkToFile(Chunk* chunk) {
ALOGV("writeChunkToFile: %" PRId64 " from %s track",
chunk->mTimeStampUs, chunk->mTrack->getTrackType());
int32_t isFirstSample = true;
while (!chunk->mSamples.empty()) {
List<MediaBuffer *>::iterator it = chunk->mSamples.begin();
uint32_t tiffHdrOffset;
if (!(*it)->meta_data().findInt32(
kKeyExifTiffOffset, (int32_t*)&tiffHdrOffset)) {
tiffHdrOffset = 0;
}
bool isExif = (tiffHdrOffset > 0);
bool usePrefix = chunk->mTrack->usePrefix() && !isExif;
size_t bytesWritten;
off64_t offset = addSample_l(*it, usePrefix, tiffHdrOffset, &bytesWritten);
if (chunk->mTrack->isHeic()) {
chunk->mTrack->addItemOffsetAndSize(offset, bytesWritten, isExif);
} else if (isFirstSample) {
chunk->mTrack->addChunkOffset(offset);
isFirstSample = false;
}
(*it)->release();
(*it) = NULL;
chunk->mSamples.erase(it);
}
chunk->mSamples.clear();
}
现在重点来看 addSample_l(…) 写数据到文件。usePrefix 为 true 的时候调用 addMultipleLengthPrefixedSamples_l(…) 进一步处理;usePrefix 为 false 时,tiffHdrOffset 大于 0 就将 exif tiff 头偏移量写入文件,写入前调用 htonl(…) 将 tiffHdrOffset 转换成无符号长整型的网络字节序(大端字节序)。接着将 MediaBuffer 中的数据写入文件。
frameworks/av/media/libstagefright/MPEG4Writer.cpp
off64_t MPEG4Writer::addSample_l(
MediaBuffer *buffer, bool usePrefix,
uint32_t tiffHdrOffset, size_t *bytesWritten) {
off64_t old_offset = mOffset;
if (usePrefix) {
addMultipleLengthPrefixedSamples_l(buffer);
} else {
if (tiffHdrOffset > 0) {
tiffHdrOffset = htonl(tiffHdrOffset);
::write(mFd, &tiffHdrOffset, 4); // exif_tiff_header_offset field
mOffset += 4;
}
::write(mFd,
(const uint8_t *)buffer->data() + buffer->range_offset(),
buffer->range_length());
mOffset += buffer->range_length();
}
*bytesWritten = mOffset - old_offset;
return old_offset;
}
调用 getNextNALUnit(…) 搜索下一个 NALU,如果待写入样本 MediaBuffer 中含有多个 NALU,就逐个根据 NALU 的位置、长度参数构造一个新的 MediaBuffer,然后调用 addLengthPrefixedSample_l(…) 真正去写入。如果数据中已经没有下一个 NALU 了,最后再将当前的 NALU 调用 addLengthPrefixedSample_l(…) 写入文件。
addLengthPrefixedSample_l(…) 中根据是否使用 4 比特 NAL 长度,决定长度占用四个字节,还是两个字节写入文件,最后把 NALU 的数据写入文件。
frameworks/av/media/libstagefright/MPEG4Writer.cpp
void MPEG4Writer::addMultipleLengthPrefixedSamples_l(MediaBuffer *buffer) {
const uint8_t *dataStart = (const uint8_t *)buffer->data() + buffer->range_offset();
const uint8_t *currentNalStart = dataStart;
const uint8_t *nextNalStart;
const uint8_t *data = dataStart;
size_t nextNalSize;
size_t searchSize = buffer->range_length();
while (getNextNALUnit(&data, &searchSize, &nextNalStart,
&nextNalSize, true) == OK) {
size_t currentNalSize = nextNalStart - currentNalStart - 4 /* strip start-code */;
MediaBuffer *nalBuf = new MediaBuffer((void *)currentNalStart, currentNalSize);
addLengthPrefixedSample_l(nalBuf);
nalBuf->release();
currentNalStart = nextNalStart;
}
size_t currentNalOffset = currentNalStart - dataStart;
buffer->set_range(buffer->range_offset() + currentNalOffset,
buffer->range_length() - currentNalOffset);
addLengthPrefixedSample_l(buffer);
}
void MPEG4Writer::addLengthPrefixedSample_l(MediaBuffer *buffer) {
size_t length = buffer->range_length();
if (mUse4ByteNalLength) {
uint8_t x = length >> 24;
::write(mFd, &x, 1);
x = (length >> 16) & 0xff;
::write(mFd, &x, 1);
x = (length >> 8) & 0xff;
::write(mFd, &x, 1);
x = length & 0xff;
::write(mFd, &x, 1);
::write(mFd,
(const uint8_t *)buffer->data() + buffer->range_offset(),
length);
mOffset += length + 4;
} else {
CHECK_LT(length, 65536u);
uint8_t x = length >> 8;
::write(mFd, &x, 1);
x = length & 0xff;
::write(mFd, &x, 1);
::write(mFd, (const uint8_t *)buffer->data() + buffer->range_offset(), length);
mOffset += length + 2;
}
}
搜索 NALU 的代码难度不大,主要靠定位 0x00 0x00 0x01 去找到 buffer 中是否含有 NALU。
frameworks/av/media/libstagefright/foundation/avc_utils.cpp
status_t getNextNALUnit(
const uint8_t **_data, size_t *_size,
const uint8_t **nalStart, size_t *nalSize,
bool startCodeFollows) {
const uint8_t *data = *_data;
size_t size = *_size;
*nalStart = NULL;
*nalSize = 0;
if (size < 3) {
return -EAGAIN;
}
size_t offset = 0;
// A valid startcode consists of at least two 0x00 bytes followed by 0x01.
for (; offset + 2 < size; ++offset) {
if (data[offset + 2] == 0x01 && data[offset] == 0x00
&& data[offset + 1] == 0x00) {
break;
}
}
if (offset + 2 >= size) {
*_data = &data[offset];
*_size = 2;
return -EAGAIN;
}
offset += 3;
size_t startOffset = offset;
for (;;) {
while (offset < size && data[offset] != 0x01) {
++offset;
}
if (offset == size) {
if (startCodeFollows) {
offset = size + 2;
break;
}
return -EAGAIN;
}
if (data[offset - 1] == 0x00 && data[offset - 2] == 0x00) {
break;
}
++offset;
}
size_t endOffset = offset - 2;
while (endOffset > startOffset + 1 && data[endOffset - 1] == 0x00) {
--endOffset;
}
*nalStart = &data[startOffset];
*nalSize = endOffset - startOffset;
if (offset + 2 < size) {
*_data = &data[offset - 2];
*_size = size - offset + 2;
} else {
*_data = NULL;
*_size = 0;
}
return OK;
}
MPEG4Writer::writeAllChunks() 主要也是调用 findChunkToWrite(…) 去查找是否还有块没写,没写就调用 writeChunkToFile(…) 写入文件。
frameworks/av/media/libstagefright/MPEG4Writer.cpp
void MPEG4Writer::writeAllChunks() {
ALOGV("writeAllChunks");
size_t outstandingChunks = 0;
Chunk chunk;
while (findChunkToWrite(&chunk)) {
writeChunkToFile(&chunk);
++outstandingChunks;
}
sendSessionSummary();
mChunkInfos.clear();
ALOGD("%zu chunks are written in the last batch", outstandingChunks);
}
现在回过头来分析 startTracks(…)。它遍历轨迹列表,逐个调用 start(…) 启动。
frameworks/av/media/libstagefright/MPEG4Writer.cpp
status_t MPEG4Writer::startTracks(MetaData *params) {
if (mTracks.empty()) {
ALOGE("No source added");
return INVALID_OPERATION;
}
for (List<Track *>::iterator it = mTracks.begin();
it != mTracks.end(); ++it) {
status_t err = (*it)->start(params);
if (err != OK) {
for (List<Track *>::iterator it2 = mTracks.begin();
it2 != it; ++it2) {
(*it2)->stop();
}
return err;
}
}
return OK;
}
- 查找参数中是否设置开始录制时间,没设置的被置为 0;
- 如果是视频轨迹或者 heic 格式查找参数中是否设置旋转角度,设置的话将 mRotation 赋为对应的角度。
- 调用 initTrackingProgressStatus(…) 初始化轨迹进度状态。
- 如果是实时录制,并且轨迹数大于 1,获取开始时间偏移量,其小于 0 时就赋值为 kInitialDelayTimeUs(700000 Us),并将开始录制时间加上这个延迟。
- 调用 MediaSource(实际为 MediaCodecSource) start(…) 开始媒体源的一些处理。
- 启动轨迹线程,执行 MPEG4Writer::Track::ThreadWrapper(…) 函数。
frameworks/av/media/libstagefright/MPEG4Writer.cpp
status_t MPEG4Writer::Track::start(MetaData *params) {
if (!mDone && mPaused) {
mPaused = false;
mResumed = true;
return OK;
}
int64_t startTimeUs;
if (params == NULL || !params->findInt64(kKeyTime, &startTimeUs)) {
startTimeUs = 0;
}
mStartTimeRealUs = startTimeUs;
int32_t rotationDegrees;
if ((mIsVideo || mIsHeic) && params &&
params->findInt32(kKeyRotation, &rotationDegrees)) {
mRotation = rotationDegrees;
}
initTrackingProgressStatus(params);
sp<MetaData> meta = new MetaData;
if (mOwner->isRealTimeRecording() && mOwner->numTracks() > 1) {
/*
* This extra delay of accepting incoming audio/video signals
* helps to align a/v start time at the beginning of a recording
* session, and it also helps eliminate the "recording" sound for
* camcorder applications.
*
* If client does not set the start time offset, we fall back to
* use the default initial delay value.
*/
int64_t startTimeOffsetUs = mOwner->getStartTimeOffsetMs() * 1000LL;
if (startTimeOffsetUs < 0) { // Start time offset was not set
startTimeOffsetUs = kInitialDelayTimeUs;
}
startTimeUs += startTimeOffsetUs;
ALOGI("Start time offset: %" PRId64 " us", startTimeOffsetUs);
}
meta->setInt64(kKeyTime, startTimeUs);
status_t err = mSource->start(meta.get());
if (err != OK) {
mDone = mReachedEOS = true;
return err;
}
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
mDone = false;
mStarted = true;
mTrackDurationUs = 0;
mReachedEOS = false;
mEstimatedTrackSizeBytes = 0;
mMdatSizeBytes = 0;
mMaxChunkDurationUs = 0;
mLastDecodingTimeUs = -1;
pthread_create(&mThread, &attr, ThreadWrapper, this);
pthread_attr_destroy(&attr);
return OK;
}
MPEG4Writer::Track::ThreadWrapper(…) 主要为了调用 MPEG4Writer::Track 对象的 threadEntry() 函数。
frameworks/av/media/libstagefright/MPEG4Writer.cpp
// static
void *MPEG4Writer::Track::ThreadWrapper(void *me) {
Track *track = static_cast<Track *>(me);
status_t err = track->threadEntry();
return (void *)(uintptr_t)err;
}
threadEntry() 关键流程如下:
-
调用 prctl(…) 设置线程名称,根据音视频轨迹不同设置不同的名字。
-
如果是实时录制,设置线程优先级为 ANDROID_PRIORITY_AUDIO。
-
一个大的 while 循环,循环条件主要取决于从 MediaCodecSource 读取编码后的数据到 buffer。
3.1 从 buffer 中查找是否存在编码器配置数据(比如 H264 数据流里的 SPS 和 PPS),mGotAllCodecSpecificData 布尔值用来控制是否已经得到了编码器配置数据,如果已经得到下次再碰到就忽略。
3.2 由于我们配置的视频编码格式是 H264,所以这里会进入 makeAVCCodecSpecificData(…) 方法。
3.3 如果 makeAVCCodecSpecificData(…) 返回值不为 OK,说明会发生了错误就要将错误事件向上层传递(MEDIA_RECORDER_TRACK_ERROR_GENERAL)。
3.4 每帧元数据样本的大小必须小于允许的最大值,否则释放 buffer 并调用 MediaCodecSource stop() 流程。
3.5 判断元数据中是否存在 EXIF 数据。
3.6 做一个 MediaBuffer 和 Metadata 的深度拷贝,并尽快释放之前的 buffer。
3.7 最大文件大小或持续时长处理,文件大小超出限制时调用 switchFd() 切换 fd,如果切换不成功首先停止 MediaCodecSource,接着就要将错误事件(MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED)向上层传递。如果超出最大时长限制同样需要像上层传递错误事件(MEDIA_RECORDER_INFO_MAX_DURATION_REACHED),并停止 MediaCodecSource。
3.8 对于视频跳过前几个非关键帧,直到获得第一个关键帧。
3.9 如果不是 HEIC 格式,判断 stsz 表(Sample Size Box,指定了每个 Sample 的 size)表项是否为 0,如果为 0 说明刚开始录制设置开始时间戳。
3.10 如有必要,更新 ctts 表,更新 ctts 时间偏移范围。
3.11 向 stsz 表添加表项。
3.12 向 stss 表(Sync Sample Box,同步 Sample 表,存放关键帧列表,关键帧是为了支持随机访问)添加表项。
3.13 向 mChunkSamples 引用的 List<MediaBuffer *> 列表中添加拷贝后的 buffer, 调用 bufferChunk(…) 进一步处理。
-
向 stsc 表(Sample-To-Chunk Box,Sample-Chunk 映射表,MP4 通常把 Sample 封装到 Chunk 中,一个 Chunk 可能会包含一个或者几个 Sample)添加表项。
-
向 stts 表(Time-to-Sample Box,定义每个 Sample 时长)添加表项。
frameworks/av/media/libstagefright/MPEG4Writer.cpp
status_t MPEG4Writer::Track::threadEntry() {
int32_t count = 0;
const int64_t interleaveDurationUs = mOwner->interleaveDuration();
const bool hasMultipleTracks = (mOwner->numTracks() > 1);
int64_t chunkTimestampUs = 0;
int32_t nChunks = 0;
int32_t nActualFrames = 0; // frames containing non-CSD data (non-0 length)
int32_t nZeroLengthFrames = 0;
int64_t lastTimestampUs = 0; // Previous sample time stamp
int64_t lastDurationUs = 0; // Between the previous two samples
int64_t currDurationTicks = 0; // Timescale based ticks
int64_t lastDurationTicks = 0; // Timescale based ticks
int32_t sampleCount = 1; // Sample count in the current stts table entry
uint32_t previousSampleSize = 0; // Size of the previous sample
int64_t previousPausedDurationUs = 0;
int64_t timestampUs = 0;
int64_t cttsOffsetTimeUs = 0;
int64_t currCttsOffsetTimeTicks = 0; // Timescale based ticks
int64_t lastCttsOffsetTimeTicks = -1; // Timescale based ticks
int32_t cttsSampleCount = 0; // Sample count in the current ctts table entry
uint32_t lastSamplesPerChunk = 0;
if (mIsAudio) {
prctl(PR_SET_NAME, (unsigned long)"AudioTrackEncoding", 0, 0, 0);
} else if (mIsVideo) {
prctl(PR_SET_NAME, (unsigned long)"VideoTrackEncoding", 0, 0, 0);
} else {
prctl(PR_SET_NAME, (unsigned long)"MetadataTrackEncoding", 0, 0, 0);
}
if (mOwner->isRealTimeRecording()) {
androidSetThreadPriority(0, ANDROID_PRIORITY_AUDIO);
}
sp<MetaData> meta_data;
status_t err = OK;
MediaBufferBase *buffer;
const char *trackName = getTrackType();
while (!mDone && (err = mSource->read(&buffer)) == OK) {
if (buffer->range_length() == 0) {
buffer->release();
buffer = NULL;
++nZeroLengthFrames;
continue;
}
// If the codec specific data has not been received yet, delay pause.
// After the codec specific data is received, discard what we received
// when the track is to be paused.
if (mPaused && !mResumed) {
buffer->release();
buffer = NULL;
continue;
}
++count;
int32_t isCodecConfig;
if (buffer->meta_data().findInt32(kKeyIsCodecConfig, &isCodecConfig)
&& isCodecConfig) {
// if config format (at track addition) already had CSD, keep that
// UNLESS we have not received any frames yet.
// TODO: for now the entire CSD has to come in one frame for encoders, even though
// they need to be spread out for decoders.
if (mGotAllCodecSpecificData && nActualFrames > 0) {
ALOGI("ignoring additional CSD for video track after first frame");
} else {
mMeta = mSource->getFormat(); // get output format after format change
status_t err;
if (mIsAvc) {
err = makeAVCCodecSpecificData(
(const uint8_t *)buffer->data()
+ buffer->range_offset(),
buffer->range_length());
} else if (mIsHevc || mIsHeic) {
err = makeHEVCCodecSpecificData(
(const uint8_t *)buffer->data()
+ buffer->range_offset(),
buffer->range_length());
} else if (mIsMPEG4) {
copyCodecSpecificData((const uint8_t *)buffer->data() + buffer->range_offset(),
buffer->range_length());
}
}
buffer->release();
buffer = NULL;
if (OK != err) {
mSource->stop();
mOwner->notify(MEDIA_RECORDER_TRACK_EVENT_ERROR,
mTrackId | MEDIA_RECORDER_TRACK_ERROR_GENERAL, err);
break;
}
mGotAllCodecSpecificData = true;
continue;
}
// Per-frame metadata sample's size must be smaller than max allowed.
if (!mIsVideo && !mIsAudio && !mIsHeic &&
buffer->range_length() >= kMaxMetadataSize) {
ALOGW("Buffer size is %zu. Maximum metadata buffer size is %lld for %s track",
buffer->range_length(), (long long)kMaxMetadataSize, trackName);
buffer->release();
mSource->stop();
mIsMalformed = true;
break;
}
bool isExif = false;
uint32_t tiffHdrOffset = 0;
int32_t isMuxerData;
if (buffer->meta_data().findInt32(kKeyIsMuxerData, &isMuxerData) && isMuxerData) {
// We only support one type of muxer data, which is Exif data block.
isExif = isExifData(buffer, &tiffHdrOffset);
if (!isExif) {
ALOGW("Ignoring bad Exif data block");
buffer->release();
buffer = NULL;
continue;
}
}
++nActualFrames;
// Make a deep copy of the MediaBuffer and Metadata and release
// the original as soon as we can
MediaBuffer *copy = new MediaBuffer(buffer->range_length());
memcpy(copy->data(), (uint8_t *)buffer->data() + buffer->range_offset(),
buffer->range_length());
copy->set_range(0, buffer->range_length());
meta_data = new MetaData(buffer->meta_data());
buffer->release();
buffer = NULL;
if (isExif) {
copy->meta_data().setInt32(kKeyExifTiffOffset, tiffHdrOffset);
}
bool usePrefix = this->usePrefix() && !isExif;
if (usePrefix) StripStartcode(copy);
size_t sampleSize = copy->range_length();
if (usePrefix) {
if (mOwner->useNalLengthFour()) {
sampleSize += 4;
} else {
sampleSize += 2;
}
}
// Max file size or duration handling
mMdatSizeBytes += sampleSize;
updateTrackSizeEstimate();
if (mOwner->exceedsFileSizeLimit()) {
if (mOwner->switchFd() != OK) {
ALOGW("Recorded file size exceeds limit %" PRId64 "bytes",
mOwner->mMaxFileSizeLimitBytes);
mSource->stop();
mOwner->notify(
MEDIA_RECORDER_EVENT_INFO, MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED, 0);
} else {
ALOGV("%s Current recorded file size exceeds limit %" PRId64 "bytes. Switching output",
getTrackType(), mOwner->mMaxFileSizeLimitBytes);
}
copy->release();
break;
}
if (mOwner->exceedsFileDurationLimit()) {
ALOGW("Recorded file duration exceeds limit %" PRId64 "microseconds",
mOwner->mMaxFileDurationLimitUs);
mOwner->notify(MEDIA_RECORDER_EVENT_INFO, MEDIA_RECORDER_INFO_MAX_DURATION_REACHED, 0);
copy->release();
mSource->stop();
break;
}
if (mOwner->approachingFileSizeLimit()) {
mOwner->notifyApproachingLimit();
}
int32_t isSync = false;
meta_data->findInt32(kKeyIsSyncFrame, &isSync);
CHECK(meta_data->findInt64(kKeyTime, ×tampUs));
// For video, skip the first several non-key frames until getting the first key frame.
if (mIsVideo && !mGotStartKeyFrame && !isSync) {
ALOGD("Video skip non-key frame");
copy->release();
continue;
}
if (mIsVideo && isSync) {
mGotStartKeyFrame = true;
}
if (!mIsHeic) {
if (mStszTableEntries->count() == 0) {
mFirstSampleTimeRealUs = systemTime() / 1000;
mOwner->setStartTimestampUs(timestampUs);
mStartTimestampUs = timestampUs;
previousPausedDurationUs = mStartTimestampUs;
}
if (mResumed) {
......
}
......
if (mIsVideo) {
/*
* Composition time: timestampUs
* Decoding time: decodingTimeUs
* Composition time offset = composition time - decoding time
*/
int64_t decodingTimeUs;
CHECK(meta_data->findInt64(kKeyDecodingTime, &decodingTimeUs));
decodingTimeUs -= previousPausedDurationUs;
// ensure non-negative, monotonic decoding time
if (mLastDecodingTimeUs < 0) {
decodingTimeUs = std::max((int64_t)0, decodingTimeUs);
} else {
// increase decoding time by at least the larger vaule of 1 tick and
// 0.1 milliseconds. This needs to take into account the possible
// delta adjustment in DurationTicks in below.
decodingTimeUs = std::max(mLastDecodingTimeUs +
std::max(100, divUp(1000000, mTimeScale)), decodingTimeUs);
}
mLastDecodingTimeUs = decodingTimeUs;
timestampDebugEntry.dts = decodingTimeUs;
timestampDebugEntry.frameType = isSync ? "Key frame" : "Non-Key frame";
// Insert the timestamp into the mTimestampDebugHelper
if (mTimestampDebugHelper.size() >= kTimestampDebugCount) {
mTimestampDebugHelper.pop_front();
}
mTimestampDebugHelper.push_back(timestampDebugEntry);
cttsOffsetTimeUs =
timestampUs + kMaxCttsOffsetTimeUs - decodingTimeUs;
if (WARN_UNLESS(cttsOffsetTimeUs >= 0LL, "for %s track", trackName)) {
copy->release();
mSource->stop();
mIsMalformed = true;
break;
}
timestampUs = decodingTimeUs;
ALOGV("decoding time: %" PRId64 " and ctts offset time: %" PRId64,
timestampUs, cttsOffsetTimeUs);
// Update ctts box table if necessary
currCttsOffsetTimeTicks =
(cttsOffsetTimeUs * mTimeScale + 500000LL) / 1000000LL;
if (WARN_UNLESS(currCttsOffsetTimeTicks <= 0x0FFFFFFFFLL, "for %s track", trackName)) {
copy->release();
mSource->stop();
mIsMalformed = true;
break;
}
if (mStszTableEntries->count() == 0) {
// Force the first ctts table entry to have one single entry
// so that we can do adjustment for the initial track start
// time offset easily in writeCttsBox().
lastCttsOffsetTimeTicks = currCttsOffsetTimeTicks;
addOneCttsTableEntry(1, currCttsOffsetTimeTicks);
cttsSampleCount = 0; // No sample in ctts box is pending
} else {
if (currCttsOffsetTimeTicks != lastCttsOffsetTimeTicks) {
addOneCttsTableEntry(cttsSampleCount, lastCttsOffsetTimeTicks);
lastCttsOffsetTimeTicks = currCttsOffsetTimeTicks;
cttsSampleCount = 1; // One sample in ctts box is pending
} else {
++cttsSampleCount;
}
}
// Update ctts time offset range
if (mStszTableEntries->count() == 0) {
mMinCttsOffsetTicks = currCttsOffsetTimeTicks;
mMaxCttsOffsetTicks = currCttsOffsetTimeTicks;
} else {
if (currCttsOffsetTimeTicks > mMaxCttsOffsetTicks) {
mMaxCttsOffsetTicks = currCttsOffsetTimeTicks;
} else if (currCttsOffsetTimeTicks < mMinCttsOffsetTicks) {
mMinCttsOffsetTicks = currCttsOffsetTimeTicks;
mMinCttsOffsetTimeUs = cttsOffsetTimeUs;
}
}
}
if (mOwner->isRealTimeRecording()) {
if (mIsAudio) {
updateDriftTime(meta_data);
}
}
if (WARN_UNLESS(timestampUs >= 0LL, "for %s track", trackName)) {
copy->release();
mSource->stop();
mIsMalformed = true;
break;
}
ALOGV("%s media time stamp: %" PRId64 " and previous paused duration %" PRId64,
trackName, timestampUs, previousPausedDurationUs);
if (timestampUs > mTrackDurationUs) {
mTrackDurationUs = timestampUs;
}
// We need to use the time scale based ticks, rather than the
// timestamp itself to determine whether we have to use a new
// stts entry, since we may have rounding errors.
// The calculation is intended to reduce the accumulated
// rounding errors.
currDurationTicks =
((timestampUs * mTimeScale + 500000LL) / 1000000LL -
(lastTimestampUs * mTimeScale + 500000LL) / 1000000LL);
if (currDurationTicks < 0LL) {
ALOGE("do not support out of order frames (timestamp: %lld < last: %lld for %s track",
(long long)timestampUs, (long long)lastTimestampUs, trackName);
copy->release();
mSource->stop();
mIsMalformed = true;
break;
}
// if the duration is different for this sample, see if it is close enough to the previous
// duration that we can fudge it and use the same value, to avoid filling the stts table
// with lots of near-identical entries.
// "close enough" here means that the current duration needs to be adjusted by less
// than 0.1 milliseconds
if (lastDurationTicks && (currDurationTicks != lastDurationTicks)) {
int64_t deltaUs = ((lastDurationTicks - currDurationTicks) * 1000000LL
+ (mTimeScale / 2)) / mTimeScale;
if (deltaUs > -100 && deltaUs < 100) {
// use previous ticks, and adjust timestamp as if it was actually that number
// of ticks
currDurationTicks = lastDurationTicks;
timestampUs += deltaUs;
}
}
mStszTableEntries->add(htonl(sampleSize));
if (mStszTableEntries->count() > 2) {
// Force the first sample to have its own stts entry so that
// we can adjust its value later to maintain the A/V sync.
if (mStszTableEntries->count() == 3 || currDurationTicks != lastDurationTicks) {
addOneSttsTableEntry(sampleCount, lastDurationTicks);
sampleCount = 1;
} else {
++sampleCount;
}
}
if (mSamplesHaveSameSize) {
if (mStszTableEntries->count() >= 2 && previousSampleSize != sampleSize) {
mSamplesHaveSameSize = false;
}
previousSampleSize = sampleSize;
}
ALOGV("%s timestampUs/lastTimestampUs: %" PRId64 "/%" PRId64,
trackName, timestampUs, lastTimestampUs);
lastDurationUs = timestampUs - lastTimestampUs;
lastDurationTicks = currDurationTicks;
lastTimestampUs = timestampUs;
if (isSync != 0) {
addOneStssTableEntry(mStszTableEntries->count());
}
if (mTrackingProgressStatus) {
if (mPreviousTrackTimeUs <= 0) {
mPreviousTrackTimeUs = mStartTimestampUs;
}
trackProgressStatus(timestampUs);
}
}
if (!hasMultipleTracks) {
......
}
mChunkSamples.push_back(copy);
if (mIsHeic) {
bufferChunk(0 /*timestampUs*/);
++nChunks;
} else if (interleaveDurationUs == 0) {
addOneStscTableEntry(++nChunks, 1);
bufferChunk(timestampUs);
} else {
if (chunkTimestampUs == 0) {
chunkTimestampUs = timestampUs;
} else {
int64_t chunkDurationUs = timestampUs - chunkTimestampUs;
if (chunkDurationUs > interleaveDurationUs) {
if (chunkDurationUs > mMaxChunkDurationUs) {
mMaxChunkDurationUs = chunkDurationUs;
}
++nChunks;
if (nChunks == 1 || // First chunk
lastSamplesPerChunk != mChunkSamples.size()) {
lastSamplesPerChunk = mChunkSamples.size();
addOneStscTableEntry(nChunks, lastSamplesPerChunk);
}
bufferChunk(timestampUs);
chunkTimestampUs = timestampUs;
}
}
}
}
if (isTrackMalFormed()) {
dumpTimeStamps();
err = ERROR_MALFORMED;
}
mOwner->trackProgressStatus(mTrackId, -1, err);
if (mIsHeic) {
if (!mChunkSamples.empty()) {
bufferChunk(0);
++nChunks;
}
} else {
// Last chunk
if (!hasMultipleTracks) {
addOneStscTableEntry(1, mStszTableEntries->count());
} else if (!mChunkSamples.empty()) {
addOneStscTableEntry(++nChunks, mChunkSamples.size());
bufferChunk(timestampUs);
}
// We don't really know how long the last frame lasts, since
// there is no frame time after it, just repeat the previous
// frame's duration.
if (mStszTableEntries->count() == 1) {
lastDurationUs = 0; // A single sample's duration
lastDurationTicks = 0;
} else {
++sampleCount; // Count for the last sample
}
if (mStszTableEntries->count() <= 2) {
addOneSttsTableEntry(1, lastDurationTicks);
if (sampleCount - 1 > 0) {
addOneSttsTableEntry(sampleCount - 1, lastDurationTicks);
}
} else {
addOneSttsTableEntry(sampleCount, lastDurationTicks);
}
// The last ctts box may not have been written yet, and this
// is to make sure that we write out the last ctts box.
if (currCttsOffsetTimeTicks == lastCttsOffsetTimeTicks) {
if (cttsSampleCount > 0) {
addOneCttsTableEntry(cttsSampleCount, lastCttsOffsetTimeTicks);
}
}
mTrackDurationUs += lastDurationUs;
}
mReachedEOS = true;
sendTrackSummary(hasMultipleTracks);
ALOGI("Received total/0-length (%d/%d) buffers and encoded %d frames. - %s",
count, nZeroLengthFrames, mStszTableEntries->count(), trackName);
if (mIsAudio) {
ALOGI("Audio track drift time: %" PRId64 " us", mOwner->getDriftTimeUs());
}
if (err == ERROR_END_OF_STREAM) {
return OK;
}
return err;
}
- 如果 mCodecSpecificData 不为空说明已经有 Codec 配置数据了(H264 主要为PPS & SPS),直接返回。
- 入参 size 小于 4,长度太小,打印错误 Log 后返回。
- 判断 data 中的数据前四个字节是否以 0x00 0x00 0x00 0x01 开始,如果不是就调用 copyAVCCodecSpecificData(…) 进一步处理。
- 调用 parseAVCCodecSpecificData(…) 解析配置数据。
- 配置数据 header 前五个字节赋相应的值。
frameworks/av/media/libstagefright/MPEG4Writer.cpp
status_t MPEG4Writer::Track::makeAVCCodecSpecificData(
const uint8_t *data, size_t size) {
if (mCodecSpecificData != NULL) {
ALOGE("Already have codec specific data");
return ERROR_MALFORMED;
}
if (size < 4) {
ALOGE("Codec specific data length too short: %zu", size);
return ERROR_MALFORMED;
}
// Data is in the form of AVCCodecSpecificData
if (memcmp("\x00\x00\x00\x01", data, 4)) {
return copyAVCCodecSpecificData(data, size);
}
if (parseAVCCodecSpecificData(data, size) != OK) {
return ERROR_MALFORMED;
}
// ISO 14496-15: AVC file format
mCodecSpecificDataSize += 7; // 7 more bytes in the header
mCodecSpecificData = malloc(mCodecSpecificDataSize);
if (mCodecSpecificData == NULL) {
mCodecSpecificDataSize = 0;
ALOGE("Failed allocating codec specific data");
return NO_MEMORY;
}
uint8_t *header = (uint8_t *)mCodecSpecificData;
header[0] = 1; // version
header[1] = mProfileIdc; // profile indication
header[2] = mProfileCompatible; // profile compatibility
header[3] = mLevelIdc;
// 6-bit '111111' followed by 2-bit to lengthSizeMinuusOne
if (mOwner->useNalLengthFour()) {
header[4] = 0xfc | 3; // length size == 4 bytes
} else {
header[4] = 0xfc | 1; // length size == 2 bytes
}
// 3-bit '111' followed by 5-bit numSequenceParameterSets
int nSequenceParamSets = mSeqParamSets.size();
header[5] = 0xe0 | nSequenceParamSets;
header += 6;
for (List<AVCParamSet>::iterator it = mSeqParamSets.begin();
it != mSeqParamSets.end(); ++it) {
// 16-bit sequence parameter set length
uint16_t seqParamSetLength = it->mLength;
header[0] = seqParamSetLength >> 8;
header[1] = seqParamSetLength & 0xff;
// SPS NAL unit (sequence parameter length bytes)
memcpy(&header[2], it->mData, seqParamSetLength);
header += (2 + seqParamSetLength);
}
// 8-bit nPictureParameterSets
int nPictureParamSets = mPicParamSets.size();
header[0] = nPictureParamSets;
header += 1;
for (List<AVCParamSet>::iterator it = mPicParamSets.begin();
it != mPicParamSets.end(); ++it) {
// 16-bit picture parameter set length
uint16_t picParamSetLength = it->mLength;
header[0] = picParamSetLength >> 8;
header[1] = picParamSetLength & 0xff;
// PPS Nal unit (picture parameter set length bytes)
memcpy(&header[2], it->mData, picParamSetLength);
header += (2 + picParamSetLength);
}
return OK;
}
Track::bufferChunk(…) 内部调用 MPEG4Writer::bufferChunk(…) 进一步处理,调用前先构建一个 Chunk 入参,最后处理完后清空 mChunkSamples。
frameworks/av/media/libstagefright/MPEG4Writer.cpp
void MPEG4Writer::Track::bufferChunk(int64_t timestampUs) {
ALOGV("bufferChunk");
Chunk chunk(this, timestampUs, mChunkSamples);
mOwner->bufferChunk(chunk);
mChunkSamples.clear();
}
MPEG4Writer::bufferChunk(…) 中首先遍历 mChunkInfos 这个 List 列表,找到列表中和当前轨迹匹配的项,然后将入参中的块添加到 ChunkInfo 结构的 mChunks(List) 这个块列表中。最后要在条件变量 mChunkReadyCondition 上调用 signal() 方法,告诉其他在这个条件变量上等待的线程继续执行。回忆前面的分析,不难知道实际上这里的调用会让阻塞的 MPEG4Writer::threadFunc() 内部继续执行,也就是其先查找到块,然后把块写入到文件。
frameworks/av/media/libstagefright/MPEG4Writer.cpp
void MPEG4Writer::bufferChunk(const Chunk& chunk) {
ALOGV("bufferChunk: %p", chunk.mTrack);
Mutex::Autolock autolock(mLock);
CHECK_EQ(mDone, false);
for (List<ChunkInfo>::iterator it = mChunkInfos.begin();
it != mChunkInfos.end(); ++it) {
if (chunk.mTrack == it->mTrack) { // Found owner
it->mChunks.push_back(chunk);
mChunkReadyCondition.signal();
return;
}
}
CHECK(!"Received a chunk for a unknown track");
}
现在来分析 MediaSource(实际为 MediaCodecSource) start(…) 开始媒体源的一些处理干了些什么?
MediaCodecSource::start(…) 内部首先构建了一条 AMessage,最终处理是在 MediaCodecSource::onMessageReceived(…) 中。消息标志为 kWhatStart。并且调用 postSynchronouslyAndReturnError(…) 同步等待处理结果。MediaCodecSource::onMessageReceived(…) 接收到消息后调用 MediaCodecSource::onStart(…) 进一步处理。
frameworks/av/media/libstagefright/MediaCodecSource.cpp
status_t MediaCodecSource::start(MetaData* params) {
sp<AMessage> msg = new AMessage(kWhatStart, mReflector);
msg->setObject("meta", params);
return postSynchronouslyAndReturnError(msg);
}
......
void MediaCodecSource::onMessageReceived(const sp<AMessage> &msg) {
switch (msg->what()) {
......
case kWhatStart:
{
sp<AReplyToken> replyID;
CHECK(msg->senderAwaitsResponse(&replyID));
sp<RefBase> obj;
CHECK(msg->findObject("meta", &obj));
MetaData *params = static_cast<MetaData *>(obj.get());
sp<AMessage> response = new AMessage;
response->setInt32("err", onStart(params));
response->postReply(replyID);
break;
}
......
default:
TRESPASS();
}
}
- 由于 EOS 错误,编码器正在停止中则无法启动,直接返回 INVALID_OPERATION。
- 在入参元数据中查找开始时间。
- 当前场景下显然是使用 Surface 作为输入,调用 MediaCodec::setParameters(…) 给编码器配置参数。
- 最后将开始标志 mStarted 置为 true。
frameworks/av/media/libstagefright/MediaCodecSource.cpp
status_t MediaCodecSource::onStart(MetaData *params) {
if (mStopping || mOutput.lock()->mEncoderReachedEOS) {
ALOGE("Failed to start while we're stopping or encoder already stopped due to EOS error");
return INVALID_OPERATION;
}
int64_t startTimeUs;
if (params == NULL || !params->findInt64(kKeyTime, &startTimeUs)) {
startTimeUs = -1LL;
}
if (mStarted) {
......
}
ALOGI("MediaCodecSource (%s) starting", mIsVideo ? "video" : "audio");
status_t err = OK;
if (mFlags & FLAG_USE_SURFACE_INPUT) {
if (mEncoder != NULL) {
sp<AMessage> params = new AMessage;
params->setInt32(PARAMETER_KEY_SUSPEND, false);
if (startTimeUs >= 0) {
params->setInt64("skip-frames-before", startTimeUs);
}
mEncoder->setParameters(params);
}
} else {
......
}
ALOGI("MediaCodecSource (%s) started", mIsVideo ? "video" : "audio");
mStarted = true;
return OK;
}