最近突然想将以图片为背景录制一个静态音视频文件,比如可以录制一个音乐文件,以某个喜欢的图片为视频内容再加上录制电脑播放器的声音,就可以完成音乐文件录制。扬声器的录制模块已经有了,需要做的是将图片作为一个输入源,其思想是解封装–>解码–>转码–>编码–>写入文件,其实可以在《【音视频】使用FFMPEG读取本地|网络音视频流(3-4)》基础上进行修改,因为VStreamCaptor是一个外部输入的视频采集模块,即在VStreamCaptor采集器的视频采集线程修改部分逻辑行为即可。
主要思想是依赖于原来的采集线程方法不断向下一层传输解码之后的数据的逻辑,而图片解码一帧就到文件尾了,我的方法是存储上一帧frame和相关信息,在出现读取到文件尾时,不再解码直接传递存储的frame给下一层。下面是修改之后的captureProcess方法,可以注意注释的地方,即为修改的地方:
void VStreamCaptor::captureProcess()
{
int err = ERROR_CODE_OK;
AVPacket packet;
AVFrame* frame = av_frame_alloc();
// 因为图片只有一帧,添加一个Frame缓存,当读到文件尾之后使用缓存的Frame往后续的callback传递:转码-->编码……
AVFrame* preFrame = av_frame_alloc();
// 记录前一个packet的stream index下标,使用缓存Frame时要用来判断
int preStreamIndex = -1;
UINT interval = AV_TIME_BASE / m_fps, preTs = 0;
int64_t startTime = av_gettime();
while (m_running) {
int ret = av_read_frame(m_formatCtx, &packet);
// 单独判断读到文件尾,如果stream index吻合且callback不为空,则往下一层传递缓存frame数据
if (ret == AVERROR_EOF && preStreamIndex == m_streamIndex && m_onVideoCaptureData != nullptr) {
m_onVideoCaptureData(preFrame, m_index);
// 控制帧率
av_usleep(interval);
continue;
}
// 其他小于0的情况视为报错,退出采集循环
else if (ret < 0) {
err = ERROR_CODE_NO_CAPTURE_SOURCE;
LOGGER::Logger::log(LOGGER::LOG_TYPE_ERROR, "[%s] read frame error: %s", __FUNCTION__,
HCMDR_GET_ERROR_DESC(err));
if (m_onVideoCaptureError != nullptr) {
m_onVideoCaptureError(err, m_index);
}
break;
}
if (packet.stream_index == m_streamIndex) {
ret = avcodec_send_packet(m_decodeCtx, &packet);
while (ret >= 0) {
ret = avcodec_receive_frame(m_decodeCtx, frame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
break;
}
else if (ret < 0) {
err = ERROR_CODE_FFMPEG_DECODE_FAILED;
LOGGER::Logger::log(LOGGER::LOG_TYPE_ERROR, "[%s] decode packet error: %s", __FUNCTION__,
HCMDR_GET_ERROR_DESC(err));
if (m_onVideoCaptureError != nullptr) {
m_onVideoCaptureError(err, m_index);
}
break;
}
if (m_onVideoCaptureData != nullptr) {
m_onVideoCaptureData(frame, m_index);
}
// 从解码frame中拷贝数据和属性到缓存frame中
av_frame_ref(preFrame, frame);
AVRational timebaseQ = { 1, AV_TIME_BASE };
int64_t ptsTime = av_rescale_q(packet.pts, m_timebase, timebaseQ);
int64_t nowTime = av_gettime() - startTime;
if (ptsTime > nowTime) {
av_usleep(ptsTime - nowTime);
}
}
}
if (ret == AVERROR_EOF) {
avcodec_flush_buffers(m_decodeCtx);
}
// 记录packet中stream index
preStreamIndex = packet.stream_index;
av_packet_unref(&packet);
}
// 释放缓存Frame
av_frame_unref(preFrame);
av_frame_free(&preFrame);
av_frame_free(&frame);
}
到这里,可以DIY录制自己的音乐文件啦!