【二】Android MediaRecorder C++底层架构音视频处理过程和音视频同步源码分析

注:基于Android 8.1.0源码版本分析,主要是从分析C++层StageFright框架层的StageFrightRecorder类展开的,并且只分析关键相关问题的源码,其他调用和执行流程可参考上次的《MediaRecorder整体架构浅析》【Android 7.1.2源码版本】。
若有需要请查看前面章节分析:
【一】Android MediaRecorder整体架构源码浅析
一、 MediaRecorder音视频处理过程(包括了编码处理过程)

  1. 音视频处理过程图
    在这里插入图片描述

详细分析在下文第3中。
2. 音视频Puller处理过程图
在这里插入图片描述

详细分析在下文第3中。
3. 音视频处理过程源码分析
(一)、先找到视频数据来源即此处分析手机Camera传递的数据,如下:
【客户端进程中的MediaRecorder会调用BpMediaRecorder中的该方法】

status_t setCamera(const sp<hardware::ICamera>& camera, const sp<ICameraRecordingProxy>& proxy)
    {
    
    
        Parcel data, reply;
        data.writeInterfaceToken(IMediaRecorder::getInterfaceDescriptor());
/** 此处通过Binder机制,将两个对象即都是Bn对象转换成了Bp对象传递给了服务器端的MediaRecorderClient中的setCamera方法,该方法也最终调用StagefrightRecorder::setCamera方法。
** /
        data.writeStrongBinder(IInterface::asBinder(camera));
        data.writeStrongBinder(IInterface::asBinder(proxy));
        remote()->transact(SET_CAMERA, data, &reply);
        return reply.readInt32();
    }

【服务器端的StagefrightRecorder中】

status_t StagefrightRecorder::setCamera(const sp<hardware::ICamera> &camera,
                                        const sp<ICameraRecordingProxy> &proxy) {
    
    
/** 服务器端将客户端Native层的Camera的Bp(ICamera)及其录制Bp(ICameraRecordingProxy)代理对象即两个Bp对象实例保存下来。这就是视频数据的来源。 
**/
    mCamera = camera;
    mCameraProxy = proxy;
    return OK;
}

(二)、接着分析prepare状态:最后会执行该方法进行准备

status_t StagefrightRecorder::prepareInternal() {
    
    
    // Get UID and PID here for permission checking
    mClientUid = IPCThreadState::self()->getCallingUid();
    mClientPid = IPCThreadState::self()->getCallingPid();

    status_t status = OK;

    switch (mOutputFormat) {
    
    
        case OUTPUT_FORMAT_DEFAULT:
        case OUTPUT_FORMAT_THREE_GPP:
        case OUTPUT_FORMAT_MPEG_4:
        case OUTPUT_FORMAT_WEBM:
// 此处分析MPEG4
            status = setupMPEG4orWEBMRecording();
            break;
    }

    ALOGV("Recording frameRate: %d captureFps: %f",
            mFrameRate, mCaptureFps);

    return status;
}

(三)、setupMPEG4orWEBMRecording()如下:

status_t StagefrightRecorder::setupMPEG4orWEBMRecording() {
    
    
// m_ptr->decStrong(this);
mWriter.clear();
// 总的码率
    mTotalBitRate = 0;

    status_t err = OK;
    sp<MediaWriter> writer;
    sp<MPEG4Writer> mp4writer;
    if (mOutputFormat == OUTPUT_FORMAT_WEBM) {
    
    
        writer = new WebmWriter(mOutputFd);
} else {
    
    
/** 此处分析MPEG4,工厂new一个MPEG4Writer对象,是对MPEG4Writer类的MPEG4文件格式写入处理的实例化对象,传入写入指定的待写文件描述符,用户打开、写入编码后数据文件的使用。 
** /
        writer = mp4writer = AVFactory::get()->CreateMPEG4Writer(mOutputFd);
    }

if (mVideoSource < VIDEO_SOURCE_LIST_END) {
/** 此处是默认设置编码器 如有有必要的话,里面会有一些配置参数的判断是否为默认值进行设置的,此处直接是采用H.264编码格式。
**/
        setDefaultVideoEncoderIfNecessary();

// 第一处
/** 该方法主要是根据上层对应的视频源设置枚举值进行创建该对象来读取Camera中捕获的数据源,当然只有音频录制时不会创建该对象的,也不需要
并且此对象实际上是返回的CameraSource实例:
class CameraSource : public MediaSource, public MediaBufferObserver
因此可以对Camera中捕获的视频源数据进行处理。
最终会根据一个【mCaptureFpsEnable】参数来判断是否创建时光流逝录影CameraSourceTimeLapse【继承CameraSource】,默认不使用。
**/  
        sp<MediaSource> mediaSource;
        err = setupMediaSource(&mediaSource);
        if (err != OK) {
    
    
            return err;
        }
// 第二处
/**
此处初始化了一个视频编码器,传入了一个CameraSource类型的Camera媒体源数据对象,
该方法中有:sp<MetaData> meta = cameraSource->getFormat(); 该调用即是从Camera捕获的视频源数据中获取视频源数据的长宽等一些该视频格式信息。然后又根据一些变量值处理来设置视频最终的格式数据等。
然后:    sp<MediaCodecSource> encoder = MediaCodecSource::Create(
            mLooper, format, cameraSource, mPersistentSurface, flags);
此mPersistentSurface从最开始的设置来分析该对象为空,camera时此处变量可不会理会。
此句代码则根据上面的视频格式和配置参数数据等真正创建了最终的对应编码器:
struct MediaCodecSource : public MediaSource, public MediaBufferObserver;
此处能够看得出来刚好与上面的CameraSource视频数据来源对象的接口一致的,这样在MediaCodecSource里面就能与CameraSource做消息传递交互和数据的获取等操作了,这样就把视频源数据和视频编码器绑定了。然后MediaCodec就可以不断的从CameraSource中拉取视频源数据处理了。
这里面还有一套ALooper、AHandler和AMessage机制,即可以看做是java层的那套Handler的消息机制呗,通过回调来处理事件和数据等,如此就可以异步处理了。

具体实现逻辑是:
MediaCodecSource里面的Handler机制,有一个Puller继承AHandler的实例,该实例拥有了CameraSource视频来源对象实例,如此就能通过控制和调用其对应方法进行控制CameraSource的行为并且也可以读取其视频源数据,如:
status_t err = mSource->start(static_cast<MetaData *>(obj.get()));通知开始录制。
            mSource->stop();通知结束停止录制。
            status_t err = mSource->read(&mbuf);获取CameraSource里面缓存的Buffer源数据来进行编码处理。
**/
        sp<MediaCodecSource> encoder;
        err = setupVideoEncoder(mediaSource, &encoder);
        if (err != OK) {
    
    
            return err;
        }
// 第三处
/**
然后将编码器encoder添加到MPEG4Writer的writer对象中,
Track *track = new Track(this /** writer实例*/, source /** encoder实例*/, 1 + mTracks.size()); 并且将writer和encoder都放入了一个Track中,如此就能使用Track来对writer、encoder、CameraSource进行操作处理的能力,最终写入编码后的数据也是由track来完成的。该track会被放入List<Track *> mTracks;中缓存起来。每个音频源和视频源都对应有一个Track对象实例来处理。
**/
        writer->addSource(encoder);
// 赋值缓存给全局视频源编码器
        mVideoEncoderSource = encoder;
// 总的比特率即码率
        mTotalBitRate += mVideoBitRate;
    }

    if (mOutputFormat != OUTPUT_FORMAT_WEBM) {
    
    
        // Audio source is added at the end if it exists.
        // This help make sure that the "recoding" sound is suppressed for
        // camcorder applications in the recorded files.
        // TODO Audio source is currently unsupported for webm output; vorbis encoder needed.
        // disable audio for time lapse recording
        bool disableAudio = mCaptureFpsEnable && mCaptureFps < mFrameRate;
        if (!disableAudio && mAudioSource != AUDIO_SOURCE_CNT) {
    
    
/** 创建了一个音频源编码器,与上面那个视频源差不多,也是先创建实现了MediaSource的AudioSource   (该对象初始化时创建了mRecord = new AudioRecord()该对象,其内部又初始化了mAudioRecordThread = new AudioRecordThread(*this, threadCanCallJava);开启新线程方式来获取audio数据源,然后通过const sp<IAudioFlinger>& audioFlinger = AudioSystem::get_audio_flinger();通过Binder机制获取audioFlinger的代理对象,再通过sp<IAudioRecord> record = audioFlinger->openRecord方法得到AudioFlinger服务器端的AudioRecord的代理对象)   对象实例audioSource【其中参数mAudioRecord是获取数据源通道类型是通过StagefrightRecorder::setAudioSource(audio_source_t as)设置的对应java侧层API】和使用sp<MediaCodecSource> audioEncoder =
            MediaCodecSource::Create(mLooper, format, audioSource);创建了audioEncoder编码实例,并放入了writer对象中,并且新放入了一个新的Track对象中来单独对音频源数据处理。*/
            err = setupAudioEncoder(writer);
            if (err != OK) return err;
// 总的比特率即码率
            mTotalBitRate += mAudioBitRate;
        }

        if (mCaptureFpsEnable) {
    
    
// 设置视频捕获帧率
            mp4writer->setCaptureRate(mCaptureFps);
        }

        if (mInterleaveDurationUs > 0) {
    
    
            mp4writer->setInterleaveDuration(mInterleaveDurationUs);
        }
        if (mLongitudex10000 > -3600000 && mLatitudex10000 > -3600000) {
    
    
// 定位信息
            mp4writer->setGeoData(mLatitudex10000, mLongitudex10000);
        }
    }
    if (mMaxFileDurationUs != 0) {
    
    
        writer->setMaxFileDuration(mMaxFileDurationUs);
    }
    if (mMaxFileSizeBytes != 0) {
    
    
        writer->setMaxFileSize(mMaxFileSizeBytes);
    }
    if (mVideoSource == VIDEO_SOURCE_DEFAULT
            || mVideoSource == VIDEO_SOURCE_CAMERA) {
    
    
        mStartTimeOffsetMs = mEncoderProfiles->getStartTimeOffsetMs(mCameraId);
    } else if (mVideoSource == VIDEO_SOURCE_SURFACE) {
    
    
        // surface source doesn't need large initial delay
// 设置开始时间偏移200ms
        mStartTimeOffsetMs = 200;
    }
if (mStartTimeOffsetMs > 0) {
    
    
// 传入进write
        writer->setStartTimeOffsetMs(mStartTimeOffsetMs);
    }
// 设置监听回调对象,通过该对象C++层就可以把native事件一路回调到JAVA层
    writer->setListener(mListener);
    mWriter = writer;
    return OK;
}

(四)、start方法开始分析:(因为要是Camera类型VIDEO_SOURCE_CAMERA的视频源,不会直接prepare的,只有当调用了start之后才会通过判断prepare),如下:
开始录制是比较复杂的,如下处理分析:

status_t StagefrightRecorder::start() {
    
    
    ALOGV("start");
    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;
        }
    }
    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;
            }
/** 创建一个数据源格式的元数据对象,用以保存MPEG4格式的元数据即格式数据如视频开始时间、视频文件类型,视频总比特率等,以此来获取视频数据的特定格式信息。**/
            sp<MetaData> meta = new MetaData;
            setupMPEG4orWEBMMetaData(&meta);

/**  然后调用MPEG4的start函数并传入该meta对象,以此开始录制音视频。
内部start方法处理基本如下:封装box结构的视频格式数据,然后
调用非常重要的 【startWriterThread()】开启另一个线程来进行不断从CameraSource的read函数中获取从Driver层返回的音视频源数据,分别在各自track中先处理。
然后还会调用【startTracks(param);】内部会遍历所有的track即音视频轨道的音视频源数据追踪对象让其各自都立即开始,【for (List<Track *>::iterator it = mTracks.begin();
         it != mTracks.end(); ++it) {
        status_t err = (*it)->start(params);】

并且各自的Track实例中都会有
    status_t err = mSource->start(meta.get());如此操作,而这个source就是此前分析过的encoder编解码器,如此就会最终调用CameraSource.start()真正的开始录制。
然后通过C++层的Handler消息机制进行异步事件和数据的传递等操作
**/
            status = mWriter->start(meta.get());
            break;
        }
// ….. 省略部分代码
       
    }
if ((status == OK) && (!mStarted)) {
    
    
        mAnalyticsDirty = true;
// 然后标记当前状态为开始start
        mStarted = true;
    }
    return status;
}

(五)、前面都是准备阶段和开始节点阶段,接着分析正式进入音视频数据处理流程:如下
从MPEG4Writer.start()开始具体分析:源码如下

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;
    }
// 设置是否使用64位偏移数据格式保存数据,默认32位
    int32_t use64BitOffset;
    if (param &&
        param->findInt32(kKey64BitFileOffset, &use64BitOffset) &&
        use64BitOffset) {
    
    
        mUse32BitOffset = false;
    }
// 默认32位数据存在格式处理
    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;
        }
    }
// 默认启用4字节的NAL格式长度
    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);

    /*
     * When the requested file size limit is small, the priority
     * is to meet the file size limit requirement, rather than
     * to make the file streamable. mStreamableFile does not tell
     * whether the actual recorded file is streamable or not.
     */
// 若是设置了最大文件大小限制,并且大于5M则为true,即流媒体文件,后面来用保存box到内存中等作用,默认是false。
    mStreamableFile =
        (mMaxFileSizeLimitBytes != 0 &&
         mMaxFileSizeLimitBytes >= kMinStreamableFileSizeInBytes);

mWriteMoovBoxToMemory = false;
// MP4数据格式的Box结构缓冲数据,初始化
    mMoovBoxBuffer = NULL;
    mMoovBoxBufferOffset = 0;

/**
将MP4格式的Ftyp类型的Box结构数据写入文件中:
调用各种writeXXX()方法,将其MP4相关的box格式数据写入write最终的文件中:
并且里面记录了mOffset变量即当前写入了多大的数据bytes:如下
::write(mFd, ptr, size * nmemb);
        mOffset += bytes;
**/
    writeFtypBox(param);

    mFreeBoxOffset = mOffset;

    if (mEstimatedMoovBoxSize == 0) {
    
    
        int32_t bitRate = -1;
        if (param) {
    
    
            param->findInt32(kKeyBitRate, &bitRate);
        }
// 根据比特率值和mMaxFileSizeLimitBytes、mMaxFileDurationLimitUs来估算 MoovBox结构数据的大小
        mEstimatedMoovBoxSize = estimateMoovBoxSize(bitRate);
    }
    CHECK_GE(mEstimatedMoovBoxSize, 8);
    if (mStreamableFile) {
    
    
        // Reserve a 'free' box only for streamable file
        lseek64(mFd, mFreeBoxOffset, SEEK_SET);
        writeInt32(mEstimatedMoovBoxSize);
        write("free", 4);
        mMdatOffset = mFreeBoxOffset + mEstimatedMoovBoxSize;
} else {
    
    
// 启用这里赋值偏移
        mMdatOffset = mOffset;
    }
// 矫正最终的偏移量值
mOffset = mMdatOffset;
// 设置文件写入点指向【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;
    }
// 也非常重要的在写入数据线程开启后,就要进行追踪音视频各自的Track数据进行数据获取编码等,后面进行详细分析
    err = startTracks(param);
    if (err != OK) {
    
    
        return err;
    }

    mStarted = true;
    return OK;
}

(六)分析startWriterThread(): 开启文件写入线程,消费者和生产者模式通过信号量机制进行同步数据读取和写入。

status_t MPEG4Writer::startWriterThread() {
    
    
    ALOGV("startWriterThread");
// 初始化一些常量设置
mDone = false;
    mIsFirstChunk = true; // 标记第一个数据块
    mDriftTimeUs = 0; // 漂移时间为0
    for (List<Track *>::iterator it = mTracks.begin();
         it != mTracks.end(); ++it) {
    
    
// 将音视频追踪数据Track对象封装成ChunkInfo块信息,并记录在其各自的时间戳,用于同步处理等,然后都放入到List集合中
        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);
// 开启读写线程并执行ThreadWrapper
    pthread_create(&mThread, &attr, ThreadWrapper, this);
    pthread_attr_destroy(&attr);
    mWriterThreadStarted = true;
    return OK;
}
继续分析:
// static
void *MPEG4Writer::ThreadWrapper(void *me) {
    
    
    ALOGV("ThreadWrapper: %p", me);
MPEG4Writer *writer = static_cast<MPEG4Writer *>(me);
// 此处即调用了threadFunc()方法
    writer->threadFunc();
    return NULL;
}
则分析:此方法时真正在新线程中独立运行的
void MPEG4Writer::threadFunc() {
    
    
    ALOGV("threadFunc");

    prctl(PR_SET_NAME, (unsigned long)"MPEG4Writer", 0, 0, 0);
// 多线程运行时加锁处理
Mutex::Autolock autoLock(mLock);
// 若是没有结束状态,则一直循环去获取数据并写入文件中
while (!mDone) {
    
    
// 一个可能找到后的数据块
        Chunk chunk;
// 判断是否找到了Track对应的数据块
        bool chunkFound = false;
// 根据相应的Track对象找到对应有效的Chunk数据携带有音视频的数据块并返回
        while (!mDone && !(chunkFound = findChunkToWrite(&chunk))) {
    
    
            // 若是没有找到对应有效的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) {
    
    
// 在真实时间录制模式中,写入write操作是不加锁的,为了减少音视频数据Track线程的堵塞时间
            if (mIsRealTimeRecording) {
    
    
                mLock.unlock();
            }
// 将找到的Chunk数据块写入到文件中
            writeChunkToFile(&chunk);
            if (mIsRealTimeRecording) {
    
    
// 然后再次加锁进行循环处理
                mLock.lock();
            }
        }
    }
// 若是 mDone是true即录制结束时,将剩下的所有Chunk都进行写入文件中
    writeAllChunks();
}

先分析:chunkFound = findChunkToWrite(&chunk):

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();
// 第一个样本数据的时间戳比默认的肯定小,因此会进行重新设置此最小的时间来替换
// 以此一直找到最小时间戳那个Track数据进行记录和写入
            if (chunkIt->mTimeStampUs < minTimestampUs) {
    
    
                minTimestampUs = chunkIt->mTimeStampUs;
然后记录此需要被write的track数据
                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) {
    
    
// 再次匹配到了那个最小时间戳的那个Track数据的ChunkInfo中的第一个Chunk块数据,将其返回给调用者,使其被写入文件
            *chunk = *(it->mChunks.begin());
            it->mChunks.erase(it->mChunks.begin());
            CHECK_EQ(chunk->mTrack, track);

// 计算同一个Track的相邻两个数据块的时间戳间隔
            int64_t interChunkTimeUs =
                chunk->mTimeStampUs - it->mPrevChunkTimestampUs;
            if (interChunkTimeUs > it->mPrevChunkTimestampUs) {
    
    
// 若此时间间隔大于之前已被写入的数据块的时间戳,则将其记录给ChunkInfo的最大间隔时间戳
                it->mMaxInterChunkDurUs = interChunkTimeUs;
            }

            return true;
        }
    }

    return false;
}

再分析:writeChunkToFile(&chunk):

void MPEG4Writer::writeChunkToFile(Chunk* chunk) {
    
    
    ALOGV("writeChunkToFile: %" PRId64 " from %s track",
        chunk->mTimeStampUs, chunk->mTrack->getTrackType());

int32_t isFirstSample = true;
while (!chunk->mSamples.empty()) {
    
    
// 如果Chunk块数据中的样本流媒体缓冲数据列表不为空
        List<MediaBuffer *>::iterator it = chunk->mSamples.begin();

/** 此处是MP4,因此执行addSample_l(*it);将其媒体数据写入到文件中
即源码如下:
off64_t MPEG4Writer::addSample_l(MediaBuffer *buffer) {
    off64_t old_offset = mOffset;
// 写入媒体数据到文件中
    ::write(mFd,
          (const uint8_t *)buffer->data() + buffer->range_offset(),
          buffer->range_length());
// 记录当前写入了多大的数据bytes
    mOffset += buffer->range_length();

    return old_offset;
}
**/
        off64_t offset = (chunk->mTrack->isAvc() || chunk->mTrack->isHevc())
                                && (!chunk->mTrack->skipStartCodeSearch())
                                ? addMultipleLengthPrefixedSamples_l(*it)
                                : addSample_l(*it);
// 第一次循环时的Chunk块数据就是第一个采样数据
        if (isFirstSample) {
    
    
// 然后增加Track中ListTableEntries<uint32_t, 1> *mStcoTableEntries;变量记录其第一个Chunk的偏移量值,以便后续在MPEG4Writer.write()时进行写入。
            chunk->mTrack->addChunkOffset(offset);
            isFirstSample = false;
        }
// 将数据Buffer都已经写入文件了,因此直接释放内存即可,并将其从列表中移除
        (*it)->release();
        (*it) = NULL;
        chunk->mSamples.erase(it);
}
// 将该Chunk块数据中的所有缓冲采样列表数据都写入文件处理完后将其再次清空其列表缓冲区,使其其他地方继续使用
    chunk->mSamples.clear();
}

分析writeAllChunks():

void MPEG4Writer::writeAllChunks() {
    
    
ALOGV("writeAllChunks");
// 在停止录制时,记录未解决的Chunk块数据个数
    size_t outstandingChunks = 0;
    Chunk chunk;
while (findChunkToWrite(&chunk)) {
    
    
// 找到未解决的即未写入到文件的Chunk并将其写入文件中
        writeChunkToFile(&chunk);
        ++outstandingChunks;
    }

// 此方法只有测试模式时才执行
    sendSessionSummary();

// 清空块信息列表数据,然后就结束录制了
    mChunkInfos.clear();
    ALOGD("%zu chunks are written in the last batch", outstandingChunks);
}

(七)分析startTracks(param):

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) {
    
    
// 调用了Track的start()方法
        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;
}

分析:Track的start()方法:

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 && params && params->findInt32(kKeyRotation, &rotationDegrees)) {
    
    
        mRotation = rotationDegrees;
    }
// 初始化每多少us微秒数时进行跟踪进度状态即请求接收数据各多少微秒后调整当前状态
    initTrackingProgressStatus(params);

    sp<MetaData> meta = new MetaData;
if (mOwner->isRealTimeRecording() && mOwner->numTracks() > 1) {
    
    
// 此处作用见英文注释:大致就是音视频开始时间戳进行同步,方式是通过一个额外的延迟时间进行设置同步的。单位时微秒,默认是700ms毫秒即0.7秒
        /*
         * 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);

// 第一处
// 此句执行的MediaCodecSource类实例中的start方法即开启编解码器进行获取原始数据进行编码过程了,后续进行详细分析
    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;
// 第二处
// 开启线程在ThreadWrapper中进行数据追踪Track
    pthread_create(&mThread, &attr, ThreadWrapper, this);
    pthread_attr_destroy(&attr);

    return OK;
}

// 先分析第一处:mSource->start(meta.get());
status_t MediaCodecSource::start(MetaData* params) {
    
    
/** 发送了【kWhatStart】事件:而mReflector该变量如下初始化的, 
status_t MediaCodecSource::initEncoder() {
mReflector = new AHandlerReflector<MediaCodecSource>(this);
}
即最后会通过Handler机制调用MediaCodecSource的onMessageReceived(msg)方法。
**/
    sp<AMessage> msg = new AMessage(kWhatStart, mReflector);
    msg->setObject("meta", params);
    return postSynchronouslyAndReturnError(msg);
}

然后在onMessageReceived(msg)方法中找到该事件:

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;
// 调用onStart()方法
        response->setInt32("err", onStart(params));
        response->postReply(replyID);
        break;
}

分析onStart方法:

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)) {
    
    
// 若没有开始时间戳则默认设置为-1 微秒
        startTimeUs = -1ll;
    }

// 第一次是false
    if (mStarted) {
    
     
        // ….. 省略部分代码
        return OK;
    }

    ALOGI("MediaCodecSource (%s) starting", mIsVideo ? "video" : "audio");

    status_t err = OK;

    if (mFlags & FLAG_USE_SURFACE_INPUT) {
    
    
        // ….. 省略部分代码
} else {
    
    
// 我们使用的Camera数据源,因此运行此处
        CHECK(mPuller != NULL);
        sp<MetaData> meta = params;
        if (mSetEncoderFormat) {
    
    
// 为true,设置编码格式和编码数据空间格式数据
            if (meta == NULL) {
    
    
                meta = new MetaData;
            }
            meta->setInt32(kKeyPixelFormat, mEncoderFormat);
            meta->setInt32(kKeyColorSpace, mEncoderDataSpace);
        }
// notify只有发送【kWhatPullerNotify】事件通知的能力,并且延迟回调发送
        sp<AMessage> notify = new AMessage(kWhatPullerNotify, mReflector);
// 直接Puller的start方法
        err = mPuller->start(meta.get(), notify);
        if (err != OK) {
    
    
            return err;
        }
    }

    ALOGI("MediaCodecSource (%s) started", mIsVideo ? "video" : "audio");

    mStarted = true;
    return OK;
}

分析Puller的start方法:

status_t MediaCodecSource::Puller::start(const sp<MetaData> &meta, const sp<AMessage> &notify) {
    
    
    ALOGV("puller (%s) start", mIsAudio ? "audio" : "video");
    mLooper->start(
            false /* runOnCallingThread */,
            false /* canCallJava */,
            PRIORITY_AUDIO);
mLooper->registerHandler(this);
// 记住此处的AMessage拥有此前的发送给MediaCodecSource的【kWhatPullerNotify】事件通知的能力
mNotify = notify;

// 向Puller发送【kWhatStart】事件
    sp<AMessage> msg = new AMessage(kWhatStart, this);
    msg->setObject("meta", meta);
    return postSynchronouslyAndReturnError(msg);
}

向Puller发送【kWhatStart】事件分析:
在void MediaCodecSource::Puller::onMessageReceived(const sp &msg)该方法中会接收该事件进行处理:

case kWhatStart:
        {
    
    
            sp<RefBase> obj;
            CHECK(msg->findObject("meta", &obj));

            {
    
    
/** 在Puller内部中会有分配有Queue数据队列:如下结构体
struct Queue {
        Queue()
            : mReadPendingSince(0),
              mPaused(false),
              mPulling(false) { }
        int64_t mReadPendingSince;
        bool mPaused;
        bool mPulling;
        Vector<MediaBuffer *> mReadBuffers;

        void flush();
        // if queue is empty, return false and set *|buffer| to NULL . Otherwise, pop
        // buffer from front of the queue, place it into *|buffer| and return true.
        bool readBuffer(MediaBuffer **buffer);
        // add a buffer to the back of the queue
        void pushBuffer(MediaBuffer *mbuf);
    };
**/
                Mutexed<Queue>::Locked queue(mQueue);
                queue->mPulling = true; // 加锁后队列状态置为正在拉取数据
            }
// 此处会调用CameraSource或者AudioSource的start()方法,后面进行分别分析
            status_t err = mSource->start(static_cast<MetaData *>(obj.get()));

            if (err == OK) {
    
    
/** 执行拉取动作其实是直接执行kWhatPull事件:如下:
void MediaCodecSource::Puller::schedulePull() {
    (new AMessage(kWhatPull, this))->post();
}
**/
                schedulePull();
            }

            sp<AMessage> response = new AMessage;
            response->setInt32("err", err);

            sp<AReplyToken> replyID;
            CHECK(msg->senderAwaitsResponse(&replyID));
            response->postReply(replyID);
            break;
        }

分析mSource->start(static_cast<MetaData *>(obj.get()));会调用CameraSource或者AudioSource的start()方法,
此处先分析CameraSource的start()方法:

status_t CameraSource::start(MetaData *meta) {
    
    
    ALOGV("start");
    CHECK(!mStarted);
    if (mInitCheck != OK) {
    
    
        ALOGE("CameraSource is not initialized yet");
        return mInitCheck;
    }

    if (property_get_bool("media.stagefright.record-stats", false)) {
    
    
        mCollectStats = true;
    }

// 开始录制的开始时间戳
mStartTimeUs = 0;
// 已经获取到的输入缓冲区的个数
mNumInputBuffers = 0;
// 编码格式
mEncoderFormat = HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED;
// 编码数据空间
    mEncoderDataSpace = HAL_DATASPACE_V0_BT709;

if (meta) {
    
    
// 做一些元数据编码时的格式数据设置初始化
        int64_t startTimeUs;
        if (meta->findInt64(kKeyTime, &startTimeUs)) {
    
    
            mStartTimeUs = startTimeUs;
        }

        int32_t nBuffers;
        if (meta->findInt32(kKeyNumBuffers, &nBuffers)) {
    
    
            CHECK_GT(nBuffers, 0);
            mNumInputBuffers = nBuffers;
        }

        // apply encoder color format if specified
        if (meta->findInt32(kKeyPixelFormat, &mEncoderFormat)) {
    
    
            ALOGI("Using encoder format: %#x", mEncoderFormat);
        }
        if (meta->findInt32(kKeyColorSpace, &mEncoderDataSpace)) {
    
    
            ALOGI("Using encoder data space: %#x", mEncoderDataSpace);
        }
    }

status_t err;
// 开始进行Camera录制中
    if ((err = startCameraRecording()) == OK) {
    
    
        mStarted = true;
    }

    return err;
}

分析:startCameraRecording():

status_t CameraSource::startCameraRecording() {
    
    
    ALOGV("startCameraRecording");
    // Reset the identity to the current thread because media server owns the
    // camera and recording is started by the applications. The applications
    // will connect to the camera in ICameraRecordingProxy::startRecording.
    int64_t token = IPCThreadState::self()->clearCallingIdentity();
    status_t err;

if (mVideoBufferMode == hardware::ICamera::VIDEO_BUFFER_MODE_BUFFER_QUEUE) {
    
    
// 此处是Camera 2.0版本的API使用Buffer Queue来传递数据,而目前不考虑这个
        // Initialize buffer queue.
        err = initBufferQueue(mVideoSize.width, mVideoSize.height, mEncoderFormat,
                (android_dataspace_t)mEncoderDataSpace,
                mNumInputBuffers > 0 ? mNumInputBuffers : 1);
        if (err != OK) {
    
    
            ALOGE("%s: Failed to initialize buffer queue: %s (err=%d)", __FUNCTION__,
                    strerror(-err), err);
            return err;
        }
} else {
    
    
// 因此本次只分析Camera 1.0的版本API
        if (mNumInputBuffers > 0) {
    
    
            err = mCamera->sendCommand(
                CAMERA_CMD_SET_VIDEO_BUFFER_COUNT, mNumInputBuffers, 0);

            // This could happen for CameraHAL1 clients; thus the failure is
            // not a fatal error
            if (err != OK) {
    
    
                ALOGW("Failed to set video buffer count to %d due to %d",
                    mNumInputBuffers, err);
            }
        }
// 设置视频编码格式
        err = mCamera->sendCommand(
            CAMERA_CMD_SET_VIDEO_FORMAT, mEncoderFormat, mEncoderDataSpace);

        // This could happen for CameraHAL1 clients; thus the failure is
        // not a fatal error
        if (err != OK) {
    
    
            ALOGW("Failed to set video encoder format/dataspace to %d, %d due to %d",
                    mEncoderFormat, mEncoderDataSpace, err);
        }

/** 创建内存块用来存储缓冲区数据作为视频原始元数据提供者,该数据就是给encoder进行编码的原始数据,并且默认32个缓冲区,该方法具体如下:
void CameraSource::createVideoBufferMemoryHeap(size_t size, uint32_t bufferCount) {
// 这是Binder机制进行传递的内存区域
    mMemoryHeapBase = new MemoryHeapBase(size * bufferCount, 0,
            "StageFright-CameraSource-BufferHeap");
    for (uint32_t i = 0; i < bufferCount; i++) {
        mMemoryBases.push_back(new MemoryBase(mMemoryHeapBase, i * size, size));
    }
}
【mMemoryHeapBase】表示:Memory used to send the buffers to encoder, where sp<IMemory> stores VideoNativeMetadata.即用于将缓冲区发送到编码器的内存,其中sp<IMemory>存储了VideoNativeMetadata。
【mMemoryBases】:就是sp<IMemory>实例
**/
        // Create memory heap to store buffers as VideoNativeMetadata.
        createVideoBufferMemoryHeap(sizeof(VideoNativeHandleMetadata), kDefaultVideoBufferCount);
    }

err = OK;



/**
由CameraSource::isCameraAvailable方法可知:
        mCamera = Camera::create(camera);
        if (mCamera == 0) return -EBUSY;
        mCameraRecordingProxy = proxy;
        mCameraFlags |= FLAGS_HOT_CAMERA;

此处可知,mCameraFlags包括了FLAGS_HOT_CAMERA
**/
    if (mCameraFlags & FLAGS_HOT_CAMERA) {
    
    
        mCamera->unlock();
        mCamera.clear();
// 因此进行此处执行, 并设置回调代理对象【通过Binder机制完成】
        if ((err = mCameraRecordingProxy->startRecording(
                new ProxyListener(this))) != OK) {
    
    
            ALOGE("Failed to start recording, received error: %s (%d)",
                    strerror(-err), err);
        }
    } else {
    
    
        mCamera->setListener(new CameraSourceListener(this));
        mCamera->startRecording();
        if (!mCamera->recordingEnabled()) {
    
    
            err = -EINVAL;
            ALOGE("Failed to start recording");
        }
    }
    IPCThreadState::self()->restoreCallingIdentity(token);
    return err;
}

分析【mCameraRecordingProxy->startRecording(new ProxyListener(this))) )】如下:

先看下listener:
它是一个Bn对象,并且是Camera进行回调到CameraSource的回调监听类,
用来接收视频一帧frame数据的回调。

class ProxyListener: public BnCameraRecordingProxyListener {
    
    
    public:
        ProxyListener(const sp<CameraSource>& source);
// 该帧数据时间戳进行回调
        virtual void dataCallbackTimestamp(int64_t timestampUs, int32_t msgType,
                const sp<IMemory> &data);
// 录制一帧视频进行了回调
        virtual void recordingFrameHandleCallbackTimestamp(int64_t timestampUs,
                native_handle_t* handle);
// 录制一帧多个时间戳数据进行回调
        virtual void recordingFrameHandleCallbackTimestampBatch(
                const std::vector<int64_t>& timestampsUs,
                const std::vector<native_handle_t*>& handles);

private:
// 持有客户端的CameraSource对象,用来进行回调给客户端
        sp<CameraSource> mSource;
    };

再分析【mCameraRecordingProxy->startRecording()】方法如下:

status_t Camera::RecordingProxy::startRecording(const sp<ICameraRecordingProxyListener>& listener)
{
    
    
ALOGV("RecordingProxy::startRecording");
// 设置缓存了录制代理监听Bn代理对象给【Camera】实例对象的变量即缓存在mRecordingProxyListener变量中。
mCamera->setRecordingProxyListener(listener);
// 重连接
mCamera->reconnect();
// 开始录制
    return mCamera->startRecording();
}
分析:mCamera->reconnect(); 如下
status_t Camera::reconnect()
{
    
    
ALOGV("reconnect");
// 请看下面接着的分析此变量的来历
    sp <::android::hardware::ICamera> c = mCamera;
if (c == 0) return NO_INIT;
// 硬件层Camera连接
    return c->connect(this);
}

此处先分析mCamera的来历不同于【Camera】类实例,它是真正的底层硬件层定义和交互的更底层Camera实例。
来自于: Camera::create(camera);
即如下一句:c->mCamera = camera;即可知此处的mCamera就是硬件层交互的Camera

// construct a camera client from an existing camera remote
sp<Camera> Camera::create(const sp<::android::hardware::ICamera>& camera)
{
    
    
     ALOGV("create");
     if (camera == 0) {
    
    
         ALOGE("camera remote is a NULL pointer");
         return 0;
     }

    sp<Camera> c = new Camera(-1);
    if (camera->connect(c) == NO_ERROR) {
    
    
        c->mStatus = NO_ERROR;
// 进行赋值远程的硬件层交互的Camera实例
        c->mCamera = camera;
        IInterface::asBinder(camera)->linkToDeath(c);
        return c;
    }
    return 0;
}

分析 mCamera->startRecording();如下:

// start recording mode, must call setPreviewTarget first
status_t Camera::startRecording()
{
    
    
    ALOGV("startRecording");
    sp <::android::hardware::ICamera> c = mCamera;
if (c == 0) return NO_INIT;
// 开始录制模式,也是通知硬件层交互的Camera实例进行录制
    return c->startRecording();
}

关于Camera开始录制视频的流程就到与硬件层交互的Camera这里了,硬件层交互的Camera后续有时间再进行分析。

!!!如此到此就真正的开启了硬件层的录制功能了,接着就是等待Driver硬件层Camera回调数据回来,此回调肯定与上面的ProxyListener监听回调类有关了,后面数据回调时再分析。

然后分析音频数据源AudioSource的start()方法:

后续的分析请查看第三章分析
【三】Android MediaRecorder C++底层架构音视频处理过程和音视频同步源码分析

MediaRecorder本系列章节内容分析已在一两年前分析完成的,当初只是用于分析的笔记记录下来,如今已走上了我向往的音视频开发领域,也调研和参与过音视频相关技术,目前喜爱C++底层音视频技术的开发,偏向于底层的音视频复用/解复用、解码/编码、推流拉流技术等,而当初分析本章内容时音视频技术积累较少,因此若本文中分析有误还请多多指教,谢谢。

过了这么久时间才分享出来的原因是,以往技术知识也向前辈们分享文章有所收获,现如今我也可以有所技术分享给需要的人,也希望可以帮忙到需要的人,技术分享帮助他人的同时也能作为自身技术掌握的总结记录。

以往忙于技术深入和研究未有所分享的技术,往后坚持有质量的技术分享。

猜你喜欢

转载自blog.csdn.net/u012430727/article/details/110942318