condition_variable必须结合unique_lock使用,用这两个就可以实现线程等待了。
unique_lock
创建时自动上锁,出了作用域就会自动解锁,也可以手动想在哪解锁就在哪解锁,比较方便
unique_lock<mutex> Lock(mtx);
condition_variable
条件变量,有很多作用。最常见的就是它的等待和唤醒作用了
std::condition_variable cv;
unique_lock<mutex> Lock2(mtx);
cv.wait(Lock2);
声明一个条件变量就可以开始用wait()等待了
cv.notify_one();
唤醒也很简单,随机唤醒一个线程
唯一需要注意的是:cv.wait()首先对传进来的锁进行解锁,然后等待阻塞至其他线程唤醒它,一醒来就会先上锁,再往下运行
整体代码
/*
* @Time: 2023.10.27
* @Author: Wu Liu
* @File: T_format9+thread2.cpp
* @Function: format conversion
* @多线程测试
*/
extern "C" {
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/avutil.h>
#include <libavutil/imgutils.h>
#include <libswscale/swscale.h>
}
#include <thread>
#include <mutex>
#include <condition_variable>
#include <iostream>
#include "SCOPEG.h"
#include <queue>
#include <time.h>
/* @T_format6.cpp
* 基本流程:
* 1.解析输入文件,获得流信息,确定音视频解码器参数、上下文。
* 2.根据输出要求配置音视频编码器参数
* 3.循环每一帧解码、再编码输出
* 4.内存清理
*/
/*多线程:
* 1.音视频读包、解码一个线程:循环解码直到完整一帧传到队列中
* 2.音视频编码、输出一个线程:多队列中拿出来一帧判断是音频、视频,再解码传出
* 3.解码时锁住帧数据队列,解完完整一帧解锁
* 4.编码获得帧数据时锁住队列,完成释放。
* 5.循环完成所有数据,再走主进程。
*/
std::mutex mtx; // 互斥量,保证线程访问的互斥
std::condition_variable cv; // 条件变量,用于线程之间的通信
int ret = 0;
queue<AVFrame*> FrameQueue;
bool End_all = false;
void decodeThread1(const std::string& inputFile) {
AVFormatContext* inputFormatContext = nullptr;
AVCodecContext* videoCodecContext = nullptr;
AVCodecContext* audioCodecContext = nullptr;
AVStream* videoStream = nullptr;
AVStream* audioStream = nullptr;
// 分配帧对象
AVFrame* videoFrame = av_frame_alloc();
AVFrame* audioFrame = av_frame_alloc();
AVPacket* inputPacket = av_packet_alloc();
ON_SCOPE_EXIT{ av_frame_free(&videoFrame); };
ON_SCOPE_EXIT{ av_frame_free(&audioFrame); };
ON_SCOPE_EXIT{ av_packet_free(&inputPacket); };
if (!videoFrame || !audioFrame || !inputPacket) {
std::cout << "分配帧对象失败" << std::endl;
return;
}
// 打开输入文件
if (avformat_open_input(&inputFormatContext, inputFile.c_str(), nullptr, nullptr) != 0) {
std::cout << "无法打开输入文件" << std::endl;
return;
}
ON_SCOPE_EXIT{ avformat_close_input(&inputFormatContext); };
// 获取流信息
if (avformat_find_stream_info(inputFormatContext, nullptr) < 0) {
std::cout << "无法获取输入文件流信息" << std::endl;
return;
}
// 查找视频流和音频流索引
int videoStreamIndex = -1;
int audioStreamIndex = -1;
for (int i = 0; i < inputFormatContext->nb_streams; i++) {
if (inputFormatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
videoStreamIndex = i;
}
else if (inputFormatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
audioStreamIndex = i;
}
}
if (videoStreamIndex == -1 || audioStreamIndex == -1) {
std::cout << "没有找到视频流" << std::endl;
return;
}
// 获取视频和音频流
videoStream = inputFormatContext->streams[videoStreamIndex];
audioStream = inputFormatContext->streams[audioStreamIndex];
// 获取视频解码器
const AVCodec* videoCodec = avcodec_find_decoder(videoStream->codecpar->codec_id);
if (!videoCodec) {
std::cout << "没有找到视频解码器" << std::endl;
return;
}
// 创建并打开视频解码器上下文
videoCodecContext = avcodec_alloc_context3(videoCodec);
if (!videoCodecContext) {
std::cout << "创建视频解码器上下文失败" << std::endl;
return;
}
ON_SCOPE_EXIT{ avcodec_free_context(&videoCodecContext); };
//视频流参数去填充上下文context
avcodec_parameters_to_context(videoCodecContext, videoStream->codecpar);
if (avcodec_open2(videoCodecContext, videoCodec, nullptr) < 0) {
std::cout << "打开视频解码器失败" << std::endl;
return;
}
// 获取音频编码器
const AVCodec* audioCodec = avcodec_find_decoder(audioStream->codecpar->codec_id);
if (!audioCodec) {
std::cout << "获取音频编码器失败" << std::endl;
return;
}
// 创建并打开音频解码器上下文
audioCodecContext = avcodec_alloc_context3(audioCodec);
if (!audioCodecContext) {
std::cout << "创建音频编码器上下文失败" << std::endl;
return;
}
ON_SCOPE_EXIT{ avcodec_free_context(&audioCodecContext); };
//音频流参数填充上下文
avcodec_parameters_to_context(audioCodecContext, audioStream->codecpar);
if (avcodec_open2(audioCodecContext, audioCodec, nullptr) < 0) {
std::cout << "打开音频编码器失败" << std::endl;
return;
}
//打印输入信息
av_dump_format(inputFormatContext, 0, inputFile.c_str(), 0);
//解码
while (av_read_frame(inputFormatContext, inputPacket) >= 0) {
if (inputPacket->stream_index == videoStreamIndex) {
ret = avcodec_send_packet(videoCodecContext, inputPacket);
if (ret < 0) {
break;
}
while (ret >= 0) {
ret = avcodec_receive_frame(videoCodecContext, videoFrame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
break;
}
else if (ret < 0) {
std::cout << "视频解码 ret 异常" << std::endl;
return;
}
//传输帧到队列中,新建一个AVFrame_变量,避免每帧使用相同的地址
videoFrame->quality = 1;//音视频标志
AVFrame* videoFrame_ = av_frame_clone(videoFrame);
//如果帧队列大于等于50就等待唤醒
unique_lock<mutex> Lock2(mtx);
while(FrameQueue.size() >= 50)
cv.wait(Lock2);
//将帧推入队列
FrameQueue.push(videoFrame_);
//推入队列后,唤醒编码线程
cv.notify_one();
break;
}
av_packet_unref(inputPacket);
}
else if (inputPacket->stream_index == audioStreamIndex) {
// 音频流处理
ret = avcodec_send_packet(audioCodecContext, inputPacket);
if (ret < 0) {
break;
}
while (ret >= 0) {
ret = avcodec_receive_frame(audioCodecContext, audioFrame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
break;
}
else if (ret < 0) {
std::cout << "音频解码 ret 异常" << std::endl;
return;
}
//传输帧到队列中,新建一个AVFrame变量
AVFrame* audioFrame_ = av_frame_clone(audioFrame);
//如果帧队列大于等于50就等待唤醒
unique_lock<mutex> Lock2(mtx);
while(FrameQueue.size() >= 50)
cv.wait(Lock2);
FrameQueue.push(audioFrame_);
//唤醒编码线程
cv.notify_one();
break;
}
av_packet_unref(inputPacket);
}
}
bool FrameQueue_is_empty = false;
mtx.lock();
if(FrameQueue.empty())
FrameQueue_is_empty = true;
mtx.unlock();
//解码完成了,队列非空则再额外唤醒最后一次,然后等待被唤醒
if (FrameQueue_is_empty)
{
cv.notify_one();
unique_lock<mutex> Lock3(mtx);
cv.wait(Lock3);
}
//设置结束全局变量,通知编码线程结束
mtx.lock();
End_all = true;
mtx.unlock();
}
void encodeThread1(const std::string& outputFileName, const std::string& Format, AVStream* audioStream) {
AVFormatContext* outputFormatContext = nullptr;
SwsContext* swsContext = nullptr;
AVCodecID videoCodecId;
AVCodecID audioCodecId;
AVPacket* videoOutputPacket = av_packet_alloc();
AVPacket* audioOutputPacket = av_packet_alloc();
ON_SCOPE_EXIT{ av_packet_free(&videoOutputPacket); };
ON_SCOPE_EXIT{ av_packet_free(&audioOutputPacket); };
if (!videoOutputPacket || !audioOutputPacket) {
std::cout << "分配帧对象失败" << std::endl;
return;
}
{ //编解码器控制
if (Format == "avi")
{
videoCodecId = AV_CODEC_ID_MPEG2VIDEO;
audioCodecId = AV_CODEC_ID_PCM_S16LE;
}
else if (Format == "mp4")
{
videoCodecId = AV_CODEC_ID_H264;
audioCodecId = AV_CODEC_ID_AAC;
}
else if (Format == "wmv")
{
videoCodecId = AV_CODEC_ID_MSMPEG4V3;
audioCodecId = AV_CODEC_ID_WMAV2;
}
else if (Format == "mkv")
{
videoCodecId = AV_CODEC_ID_H264;
audioCodecId = AV_CODEC_ID_MP3;
}
else if (Format == "flv")
{
videoCodecId = AV_CODEC_ID_H264;
audioCodecId = AV_CODEC_ID_AAC;
}
else {
std::cout << "不支持转换为这种格式" << std::endl;
return;
}
}
// 创建输出文件的上下文
avformat_alloc_output_context2(&outputFormatContext, nullptr, nullptr, outputFileName.c_str());
if (!outputFormatContext) {
std::cout << "创建输出文件的上下文失败" << std::endl;
return;
}
ON_SCOPE_EXIT{ avformat_free_context(outputFormatContext); };
// 添加视频流到输出上下文
AVStream* outVideoStream = avformat_new_stream(outputFormatContext, nullptr);
if (!outVideoStream) {
std::cout << "添加视频流到输出文件失败" << std::endl;
return;
}
outVideoStream->id = outputFormatContext->nb_streams - 1;
// avcodec_parameters_copy(outVideoStream->codecpar, videoStream->codecpar);
outVideoStream->codecpar->codec_tag = 0;
// 设置视频编码器
const AVCodec* outVideoCodec = avcodec_find_encoder(videoCodecId);
if (!outVideoCodec) {
std::cout << "设置视频编码器失败" << std::endl;
return;
}
AVCodecContext* outVideoCodecContext = avcodec_alloc_context3(outVideoCodec);
if (!outVideoCodecContext) {
std::cout << "设置视频编码器上下文失败" << std::endl;
return;
}
ON_SCOPE_EXIT{ avcodec_free_context(&outVideoCodecContext); };
//视频编码器参数设置
{
//avcodec_parameters_to_context(outVideoCodecContext, outVideoStream->codecpar);
outVideoCodecContext->codec_id = videoCodecId;
//outVideoCodecContext->time_base = videoStream->time_base;
outVideoCodecContext->time_base.den = 25;
outVideoCodecContext->time_base.num = 1;
outVideoCodecContext->gop_size = 13;
outVideoCodecContext->bit_rate = 8000000;
outVideoCodecContext->refs = 0;
outVideoCodecContext->max_b_frames = 10;
outVideoCodecContext->width = 1920;
outVideoCodecContext->height = 1080;
outVideoCodecContext->pix_fmt = AV_PIX_FMT_YUV420P;
}
//从输出上下文中复制参数到输出流
avcodec_parameters_from_context(outVideoStream->codecpar, outVideoCodecContext);
// 打开视频编码器
if (avcodec_open2(outVideoCodecContext, outVideoCodec, nullptr) < 0) {
std::cout << "无法打开视频编码器" << std::endl;
return;
}
// 添加音频流到输出文件
AVStream* outAudioStream = avformat_new_stream(outputFormatContext, nullptr);
if (!outAudioStream) {
std::cout << "添加音频流到输出文件失败" << std::endl;
return;
}
outAudioStream->id = outputFormatContext->nb_streams - 1;
//输出音频流参数复制
avcodec_parameters_copy(outAudioStream->codecpar, audioStream->codecpar);
/*outAudioStream->time_base.den = 11025;
outAudioStream->time_base.num = 256;
outAudioStream->codecpar->bit_rate = 320018;
outAudioStream->codecpar->profile = 1;
outAudioStream->codecpar->sample_rate = 44100;
outAudioStream->codecpar->frame_size = 1024;
av_channel_layout_default(&outAudioStream->codecpar->ch_layout, 2);
outAudioStream->codecpar->ch_layout.nb_channels = 3;*/
outAudioStream->codecpar->codec_tag = 0;
// 设置音频编码器
const AVCodec* outAudioCodec = avcodec_find_encoder(audioCodecId);
if (!outAudioCodec) {
std::cout << "设置音频编码器失败" << std::endl;
return;
}
AVCodecContext* outAudioCodecContext = avcodec_alloc_context3(outAudioCodec);
if (!outAudioCodecContext) {
std::cout << "设置音频编码器上下文失败" << std::endl;
return;
}
ON_SCOPE_EXIT{ avcodec_free_context(&outAudioCodecContext); };
//音频编码器参数
avcodec_parameters_to_context(outAudioCodecContext, outAudioStream->codecpar);
outAudioCodecContext->codec_id = audioCodecId;
outAudioCodecContext->time_base = audioStream->time_base;
//outAudioCodecContext->time_base.den = 51111100;
//outAudioCodecContext->time_base.num = 1;
//outAudioCodecContext->sample_rate = 43110;
outAudioCodecContext->sample_fmt = AV_SAMPLE_FMT_S16;
//av_channel_layout_default(&outAudioCodecContext->ch_layout, 2);
avcodec_parameters_from_context(outAudioStream->codecpar, outAudioCodecContext);
if (Format == "flv")
{
outAudioCodecContext->sample_fmt = AV_SAMPLE_FMT_FLTP;
//av_channel_layout_default(&outAudioCodecContext->ch_layout, audioCodecContext->ch_layout.nb_channels);
}
// 打开音频编码器
if (avcodec_open2(outAudioCodecContext, outAudioCodec, nullptr) < 0) {
std::cout << "无法打开音频编码器" << std::endl;
return;
}
// 打开输出文件
if (!(outputFormatContext->oformat->flags & AVFMT_NOFILE)) {
if (avio_open(&outputFormatContext->pb, outputFileName.c_str(), AVIO_FLAG_WRITE) < 0) {
std::cout << "无法打开输出文件" << std::endl;
return;
}
}
// 写入输出文件头
if (avformat_write_header(outputFormatContext, nullptr) < 0) {
std::cout << "无法写入输出文件头" << std::endl;
return;
}
//打印输出相关信息
av_dump_format(outputFormatContext, 0, outputFileName.c_str(), 1);
int nVideoCount = 0;
int nAudioCount = 0;
//std::this_thread::sleep_for(1s);
//开始编码
while (1) {
//等待解码唤醒
unique_lock<mutex> Lock1(mtx);
cv.wait(Lock1);
Lock1.unlock();
//循环编完队列里所有的帧
while (!FrameQueue.empty())
{
mtx.lock();
AVFrame* Frame = FrameQueue.front();
FrameQueue.pop();
mtx.unlock();
if (Frame->quality == 1) {
// 编码视频帧
Frame->pts = (int64_t)(40 * (nVideoCount) / av_q2d(outVideoCodecContext->time_base) / 1000.0);//时间
nVideoCount++;
ret = avcodec_send_frame(outVideoCodecContext, Frame);
if (ret < 0) {
break;
}
while (ret >= 0) {
ret = avcodec_receive_packet(outVideoCodecContext, videoOutputPacket);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
break;
}
else if (ret < 0) {
std::cout << "视频编码 ret 异常" << std::endl;
return;
}
av_packet_rescale_ts(videoOutputPacket, outVideoCodecContext->time_base, outVideoStream->time_base);
videoOutputPacket->stream_index = outVideoStream->index;
// 写入视频帧到输出文件
ret = av_interleaved_write_frame(outputFormatContext, videoOutputPacket);
if (ret < 0) {
break;
}
}
}
else {
// 编码音频帧
//Frame->pts = (int64_t)( (nAudioCount) / av_q2d(outAudioCodecContext->time_base) / 44100.0);//时间
Frame->pts = nAudioCount * 1024;
nAudioCount++;
ret = avcodec_send_frame(outAudioCodecContext, Frame);
if (ret < 0) {
break;
}
while (ret >= 0) {
ret = avcodec_receive_packet(outAudioCodecContext, audioOutputPacket);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
break;
}
else if (ret < 0) {
std::cout << "音频编码 ret 异常" << std::endl;
return;
}
av_packet_rescale_ts(audioOutputPacket, outAudioCodecContext->time_base, outAudioStream->time_base);
audioOutputPacket->stream_index = outAudioStream->index;
// 写入音频帧到输出文件
ret = av_interleaved_write_frame(outputFormatContext, audioOutputPacket);
if (ret < 0) {
break;
}
}
}
//释放每次新建的AVFrame
av_frame_free(&Frame);
//如果队列帧数量已经少于50就唤醒解码继续工作
if(FrameQueue.size()<50)
cv.notify_one();
}
//最后编完唤醒解码结束线程
cv.notify_one();
//结束整个编码线程
if (End_all) { break; }
}
// 写入输出文件尾部
av_write_trailer(outputFormatContext);
}
bool Format_conver(const std::string& inputFile, const std::string& outputFileName, const std::string& Format)
{
avformat_network_init(); // 初始化网络库
AVStream* audioStream = nullptr;
AVFormatContext* inputFormatContext = nullptr;
// 打开输入文件
if (avformat_open_input(&inputFormatContext, inputFile.c_str(), nullptr, nullptr) != 0) {
std::cout << "无法打开输入文件" << std::endl;
return false;
}
ON_SCOPE_EXIT{ avformat_close_input(&inputFormatContext); };
// 获取流信息
if (avformat_find_stream_info(inputFormatContext, nullptr) < 0) {
std::cout << "无法获取输入文件流信息" << std::endl;
return false;
}
// 查找视频流和音频流索引
int videoStreamIndex = -1;
int audioStreamIndex = -1;
for (int i = 0; i < inputFormatContext->nb_streams; i++) {
if (inputFormatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
videoStreamIndex = i;
}
else if (inputFormatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
audioStreamIndex = i;
}
}
if (videoStreamIndex == -1 || audioStreamIndex == -1) {
std::cout << "没有找到视频流" << std::endl;
return false;
}
// 获取视频和音频流
audioStream = inputFormatContext->streams[audioStreamIndex];
//编码线程
std::thread decodeThr(decodeThread1, inputFile);
//解码线程
std::thread encodeThr(encodeThread1, outputFileName, Format, audioStream);
decodeThr.join();
encodeThr.join();
return true;
}
int main() {
// 输入文件名和输出文件名
std::string inputFilename, outputFilename, Format;
/*std::cout << "请输入输入文件名(带后缀):";
std::cin >> inputFilename;
std::cout << "请输入输出格式(avi,mp4,wmv,mkv,flv...):";
std::cin >> Format;
std::cout << "请输入输出文件名(带后缀):";
std::cin >> outputFilename;*/
clock_t start, end;
start = clock();
inputFilename = "cartoonTrim.mp4";
Format = "avi";
outputFilename = "Multithreading.avi";
if (!Format_conver(inputFilename, outputFilename, Format)) {
std::cout << "Failed to convert!" << std::endl;
return -1;
}
std::cout << "Conversion complete!" << std::endl;
end = clock();
cout << "time = " << double(end - start) / CLOCKS_PER_SEC << "s" << endl;
return 0;
}
上一篇文章里发现一个bug,每次传入队列的帧使用的都是相同指针,由于编解码同步进行,解码一帧就会立马编码一帧,所以从结果来看也没有问题。但是加入等待机制就会出现帧覆盖的问题,导致转换后的视频出现掉帧的问题,这里每次传入都把帧克隆到一个新的AVFrame结构体里去,还额外的规定了队列的长度不超过50.设置等待机制可以有效的防止CPU空转,避免资源浪费。