视音频录制流程(二)

接续:https://blog.csdn.net/lin20044140410/article/details/80057981

前面的过程漏了一点,就是camera设备的打开过程,也就是mCamera=Camera.open(CAMERA_ID),分析的recorder.setCamera(mCamera);中的参数就是open的返回值。open()的过程还是很长的,下面只关注跟cameraService,ICameraClient相关一小部分,以便跟前面的connect中的描述可以串起来。

连接camera设备的过程,是先获取cameraservice的句柄,然后执行cameraservice的connect方法。这个过程会创建一个cameraclient实例CameraDeviceClient,并作为出参回传给调用者,也就是device指向指针的指针。

Frameworks/av/service/camera/libcameraservice/cameraservice.cpp
Status CameraService::connect(
        const sp<ICameraClient>& cameraClient,
        int cameraId,
        const String16& clientPackageName,
        int clientUid,
        int clientPid,
        /*out*/
        sp<ICamera>* device) {
    sp<Client> client = nullptr;
    ret = connectHelper<ICameraClient,Client>(cameraClient, id,
            CAMERA_HAL_API_VERSION_UNSPECIFIED, clientPackageName, clientUid, clientPid, API_1,
            /*legacyMode*/ false, /*shimUpdateOnly*/ false,
            /*out*/client);
	*device = client;
}

cameraclient的创建会根据hal版本和api版本,来确定具体的实例。

CameraService.cpp
Status CameraService::makeClient(const sp<CameraService>& cameraService,
        const sp<IInterface>& cameraCb, const String16& packageName, const String8& cameraId,
        int facing, int clientPid, uid_t clientUid, int servicePid, bool legacyMode,
        int halVersion, int deviceVersion, apiLevel effectiveApiLevel,
        /*out*/sp<BasicClient>* client) {
//默认配置下创建的是 CameraClient
*client = new  CameraClient(cameraService, tmp, packageName, cameraIdToInt(cameraId),
                        facing, clientPid, clientUid, getpid(), legacyMode);
//对于hal 3,api1,创建的 Camera2Client
*client = new Camera2Client(cameraService, tmp, packageName, cameraIdToInt(cameraId),
                        facing, clientPid, clientUid, servicePid, legacyMode);
//对于hal 3,api2,创建的 CameraDeviceClient
*client = new CameraDeviceClient(cameraService, tmp, packageName, cameraId,
                        facing, clientPid, clientUid, servicePid);
}

 

接着执行cameraclient的初始化,这个过程中会打开camera硬件设备。

 


 

设置camerasource的最后一步,camera的配置。

 

status_t CameraSource::configureCamera(
        CameraParameters* params,        int32_t width, int32_t height,        int32_t frameRate) {
//这个过程,主要是用要求的视频大小,帧率来配置camera,如果没有请求视频宽高,帧率,将跳过,使用camera setting中的默认值。
	if (width != -1 && height != -1) {…}
	if (frameRate != -1) {….}
}

这段分析都是从prepare()开始,到这里视频source已经准备好了。


 

5,继续prepare(),看下视频编码器的创建。

StagefrightRecorder.cpp
status_t StagefrightRecorder::setupVideoEncoder(
        const sp<MediaSource> &cameraSource,
        sp<MediaCodecSource> *source) {
//根据视频源camerasource,创建视频编码器。
sp<MediaCodecSource> encoder = MediaCodecSource::Create(
            mLooper, format, cameraSource, mPersistentSurface, flags);
//保存bufferproducer,用于buffer的申请。
    if (cameraSource == NULL) {
        mGraphicBufferProducer = encoder->getGraphicBufferProducer();
    }
}

 

MediaCodecSource::Create()创建一个MediaCodecSource实例,给一些变量赋值,然后初始化init()-->initEncoder()。

 

 

MediaCodecSource.cpp
status_t MediaCodecSource::initEncoder() {
	Vector<AString> matchingCodecs;
//根据设置的mime类型(MEDIA_MIMETYPE_VIDEO_MPEG4或者MEDIA_MIMETYPE_VIDEO_AVC),查找匹配的编码器。
	MediaCodecList::findMatchingCodecs(
		outputMIME.c_str(), true /* encoder */,
	((mFlags & FLAG_PREFER_SOFTWARE_CODEC) ? MediaCodecList::kPreferSoftwareCodecs : 0),
		&matchingCodecs);
//创建解码组件,执行其init(),configure()。
	mEncoder = MediaCodec::CreateByComponentName(mCodecLooper, matchingCodecs[ix]);
//kWhatEncoderActivity 消息的处理,通过feedEncoderInputBuffers填充编码输入buffer。
	mEncoderActivityNotify = new AMessage(kWhatEncoderActivity, mReflector);
	mEncoder→setCallback(mEncoderActivityNotify);
//注意configure()的最后一个参数 MediaCodec::CONFIGURE_FLAG_ENCODE表示接下来要创建的是编码组件。
	err = mEncoder->configure(  mOutputFormat,   NULL /* nativeWindow */,
                    NULL /* crypto */,   MediaCodec::CONFIGURE_FLAG_ENCODE);
//start(),表示编码组件初始化已经成功完成,可以准备编码工作了,也就是将其OMX_CommandStateSet状态置为OMX_StateIdle。
最终调用的是OMXNodeInstance.cpp中方法sendCommand()。
	err = mEncoder->start();
}

//这个方法会列出所有匹配mime格式的编码器,即是其中的参数matches,同时也会考虑优先软解,还是强制硬解。

 

void MediaCodecList::findMatchingCodecs(
        const char *mime, bool encoder, uint32_t flags,
        Vector<AString> *matches, Vector<AString> *owners) {
	const sp<IMediaCodecList> list = getInstance();
	const sp<MediaCodecInfo> info = list→getCodecInfo(matchIndex);
	AString componentName = info→getCodecName();
	matches->push(componentName);
}
MediaCodec.cpp
status_t MediaCodec::init(const AString &name, bool nameIsType, bool encoder) {
//这里返回的实际是ACodec实例。
	mCodec = GetCodecBase(name, nameIsType);
    mCodec->setCallback(
            std::unique_ptr<CodecBase::CodecCallback>(
                    new CodecCallback(new AMessage(kWhatCodecNotify, this))));
    mBufferChannel = mCodec->getBufferChannel();
    mBufferChannel->setCallback(
            std::unique_ptr<CodecBase::BufferCallback>(
                    new BufferCallback(new AMessage(kWhatCodecNotify, this))));
//在 kWhatInit消息的处理中,分配编解码组件。具体调用是mCodec->initiateAllocateComponent(format);
	sp<AMessage> msg = new AMessage(kWhatInit, this);
}

 

MediaCodec.cpp
status_t MediaCodec::configure(        const sp<AMessage> &format,        const sp<Surface> &surface,        	
const sp<ICrypto> &crypto,        const sp<IDescrambler> &descrambler,        uint32_t flags) {
//配置,启动编码组件,具体调用mCodec->initiateConfigureComponent(format);
	sp<AMessage> msg = new AMessage(kWhatConfigure, this);
}

 

Acodec.cpp
//这个函数,编解码组件配置都会执行,比较长,
status_t ACodec::configureCodec(        const char *mime, const sp<AMessage> &msg) {
	if (video) {
//确定是否使用软件渲染,是解码需要的。
		bool usingSwRenderer = false;
		if (haveNativeWindow && mComponentName.startsWith("OMX.google.")) {
			usingSwRenderer = true;
			(void)setPortMode(kPortIndexOutput, IOMX::kPortModePresetByteBuffer);
		}else if (haveNativeWindow && !storingMetadataInDecodedBuffers()) {
			err = setPortMode(kPortIndexOutput, IOMX::kPortModePresetANWBuffer);
		}
//创建编码或者解码器。
		if (encoder) {
			err = setupVideoEncoder(mime, msg, outputFormat, inputFormat);
		}else {
            		err = setupVideoDecoder(mime, msg, haveNativeWindow, usingSwRenderer, outputFormat);
        	}
	}
…...
}

 

再回到StagefrightRecorder.cpp中,前面编码组件的构建是从setupVideoEncoder()开始的,

 

 

StagefrightRecorder.cpp
status_t StagefrightRecorder::setupMPEG4orWEBMRecording() {
	sp<MediaWriter> writer;
        sp<MediaSource> mediaSource;
        err = setupMediaSource(&mediaSource);
        sp<MediaCodecSource> encoder;
        err = setupVideoEncoder(mediaSource, &encoder);
//前面设置视频source(camerasource),设置编码器都执行完了,接着是创建视频track,writer是Mpeg4Writer类型实例,
Mpeg4Writer的作用就是分别把视频track和音频track写入到chunck中,然后打包写入到文件容器。
	writer->addSource(encoder);
}
MPEG4Writer.cpp
status_t MPEG4Writer::addSource(const sp<IMediaSource> &source) {
	const char *mime;
	source->getFormat()->findCString(kKeyMIMEType, &mime);
	bool isAudio = !strncasecmp(mime, "audio/", 6);
//source是视频encorder,所以创建的视频track,如果是setupAudioEncoder()中调用addSource,传入的source是音频encorder,创建的就是音频track。
	Track *track = new Track(this, source, 1 + mTracks.size());
	mTracks.push_back(track);
}

 

prepare()执行完,接下来就是start()方法的调用。

 

 

StagefrightRecorder.cpp
status_t StagefrightRecorder::start() {
	switch (mOutputFormat) {
		        case OUTPUT_FORMAT_DEFAULT:
        	case OUTPUT_FORMAT_THREE_GPP:
        	case OUTPUT_FORMAT_MPEG_4:
        	case OUTPUT_FORMAT_WEBM:{
			sp<MetaData> meta = new MetaData;
			setupMPEG4orWEBMMetaData(&meta);
//调用mepg4writer的start()方法。
			status = mWriter->start(meta.get());
		}
	}
}

 

 

对文件的读写,前提是先了解这个文件格式。

mpeg4文件格式解析,参考:https://blog.csdn.net/chenchong_219/article/details/44263691

 

MPEG4Writer.cpp
status_t MPEG4Writer::start(MetaData *param) {
//先写入文件格式。
	writeFtypBox(param);
//为这个视频track创建一个线程,并从ThreadWrapper()函数开始运行,线程实际执行的是MPEG4Writer::threadFunc()函数,执行mp4文件的chunks的写入。
	 status_t err = startWriterThread();
//将会调用前面创建的视频track中的start()方法。
	err = startTracks(param);
}

 

 

 

重点看下Mpeg4Writer::Track的start()方法,媒体元数据的输入输出都在这里完成。

 

MPEG4Writer.cpp
status_t MPEG4Writer::Track::start(MetaData *params) {
	sp<MetaData> meta = new MetaData;
//mSource是前面StagefrightRecorder::setupVideoEncoder()的返回值MediaCodecSource实例。
	status_t err = mSource->start(meta.get());
	pthread_create(&mThread, &attr, ThreadWrapper, this);
}

 

从MediaCodecSource.cpp的start()方法,间接通过,MediaCodecSource::Puller的start(),调用了CameraSource.cpp的start()方法。
CameraSource.cpp
status_t CameraSource::start(MetaData *meta) {
	if ((err = startCameraRecording()) == OK) {
		mStarted = true;
	}
}
CameraSource.cpp
status_t CameraSource::startCameraRecording() {
//创建存储的buffers的内存块,作为本地视频元数据。
	createVideoBufferMemoryHeap(sizeof(VideoNativeHandleMetadata), kDefaultVideoBufferCount);
//启动camera录制,同时设置监听ProxyListener ,通过其回调dataCallbackTimestamp获取元数据。
	if ((err = mCameraRecordingProxy->startRecording(new ProxyListener(this))) != OK) {...}
}

 

void CameraSource::dataCallbackTimestamp(int64_t timestampUs,
        int32_t msgType __unused, const sp<IMemory> &data) {
//camera返回的数据放在列表 mFramesReceived,然后通过mFrameAvailableCondition 唤醒一个线程。
	mFramesReceived.push_back(data);
	mFrameTimes.push_back(timeUs);
	mFrameAvailableCondition.signal();
}

 

mFrameAvailableCondition.signal();唤醒了那个线程呢?前面在MPEG4Writer::Track::start()创建了线程ThreadWrapper,这个线程运行后,通过mSource->read(&buffer)来读取camerasource中的数据,当列表无数据可读时,这个线程就会在read时进入wait,然后等camera返回的数据填充到列表mFramesReceived时,在唤醒,继续读取。这样camera返回的数据就到了MPEG4Writer::Track中。

 

ThreadWrapper直接调用了threadEntry。

 

status_t MPEG4Writer::Track::threadEntry() {
//循环执行,通过camerasource的read拿到元数据。
	while (!mDone && (err = mSource->read(&buffer)) == OK) {
//执行编码。
		f (mIsMPEG4) {                    copyCodecSpecificData((const uint8_t *)buffer->data() +
			 buffer->range_offset(),                            buffer->range_length());
		}
//中间略去了很多代码,这里是根据timestampUs ,mChunkSamples 生成chunk。 先是调用MPEG4Writer::Track的bufferChunk()方法,
生成一个chunk后,再调用MPEG4Writer的bufferChunk()方法,同时把生成的chunk作为参数传递到MPEG4Writer中,
同时在MPEG4Writer::bufferChunk()中通过mChunkReadyCondition.signal()唤醒了另一个处于wait的线程。
		mChunkSamples.push_back(copy);
		bufferChunk(timestampUs);
	}
}

mChunkReadyCondition.signal()唤醒的是MPEG4Writer中线程ThreadWrapper,也就是MPEG4Writer中的threadFunc()方法。

 

void MPEG4Writer::threadFunc() {
	while (!mDone) {
//循环执行,读取MPEG4Writer::Track的bufferChunk()方法生成的chunk,
		while (!mDone && !(chunkFound = findChunkToWrite(&chunk))) {
			mChunkReadyCondition.wait(mLock);
		}
//将chunk写入文件容器。
		writeChunkToFile(&chunk);
		writeAllChunks();
	}
}
到这里完成了视音频录制的。音频的处理类似。

MediaWriter是把音视频字幕track打包到容器中,播放时的逆过程是通过MediaExtractor来提取分离出容器中的音视频track。

  对代码中类的命名说几句题外话,感觉这些大牛之所以是大牛,不仅仅在于专业技术够强,还在于对技术外的生活有很好的体察,所以他们定义的类名、方法名等做到了恰到好处,能让人望文生义,比如:MediaExtractor,其中的Extractor意思是萃取,萃取就是分离混合物中的单元,所以即使在没有具体了解MediaExtractor前,通过名字也能大概知道它的功能,就是萃取多媒体中的资源,多媒体资源都有那些呢,音频、视频、字幕,所以它的功能就是从容器中把这些资源分离出来。

  再比如Choreographer,就是画面的渲染需要vsync信号的触发,应用的画面,包括动画的步进主要是通过注册callback到Choreographer中,然后Choreographer在vsync到来时,通知感兴趣的注册者。Choreographer意思是编舞的人,就像舞台上表演者什么时候该做什么动作,收腹提臀挺胸啥的,是由编舞的人事先定好的。类比过来,手机屏幕就是舞台,该显示什么内容也是要有"舞台指导"的,也即是Choreographer,所以看的出来,写这块代码的人一定是个热爱艺术的人。

 

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值