最开始实现的格式转化,流程是输入解码一帧然后再编码这一帧再输出,发现输入解码和编码输出两个部分是互不干扰的,这样我们就可以设计两个线程来实现解码和编码同时进行提高效率。
原理
我想的原理很简单:
①解码:不断解码放入队列。结束——如果队列非空,循环等待编码完成,再设置全局结束标志。
②编码:循环队列非空即编码。结束——通过全局结束标志结束
(这里发现解码比编码快很多,如果解码线程提前结束了,队列里的内容就会置0,所以让解码跟编码线程一起结束)
中间维护一个队列以及全局变量来通信就可以非常简单的实现多线程了。
C++多线程
做简单的多线程需要用到的东西不多
1.创建线程
用函数包起来既可以了,第一个参数是函数名,后面的参数是你要传进这个函数的参数,然后在decodeThread1这个函数里实现自己的解码线程就可以了
#include <thread>
std::thread decodeThr(decodeThread1, inputFile);
2.互斥量
对于两个线程都要访问的变量,要是碰巧一起访问到了就会出问题,所以用互斥量来锁住后再访问
#include <mutex>
std::mutex mtx;
访问两个线程都可能会访问到的变量时,给互斥量上好锁再访问就没问题了
mtx.lock();
//要访问的变量
mtx.unlock();
3.线程间的通信
线程间传递信息的方式很多,我们用最简单的全局变量就可以实现通信了。
4.线程的结束方式
①join方式,主线程碰到这条语句就会停下来等这个线程结束
decodeThr.join();
②detach方式,主线程不管它继续往下运行,要是主线程结束了,就不会管它直接整个程序结束
decodeThr.detach();
5.条件变量
#include <condition_variable>
std::condition_variable cv;
实现线程等待的一个东西,比如说编码线程要解码线程解完一帧才开始,前面一段时间就得等待一会了。我试了一下,发现在我这个简单的例子上用到等待机制反而有点冗余多余了,后面就没加上。
整体代码
/*
* @Time: 2023.11.1
* @Author: Wu Liu
* @File: T_format9+thread.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 isFramedecode = false;
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);
mtx.lock();
FrameQueue.push(videoFrame_);
mtx.unlock();
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);
mtx.lock();
FrameQueue.push(audioFrame_);
mtx.unlock();
break;
}
av_packet_unref(inputPacket);
}
}
bool FrameQueue_is_empty = false;
while (!FrameQueue_is_empty)
{
mtx.lock();
if (FrameQueue.empty())
FrameQueue_is_empty = true;
mtx.unlock();
}
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;
bool FrameQueue_is_ready = false;
//std::this_thread::sleep_for(1s);
while (1) {
AVFrame* Frame=nullptr;
mtx.lock();
if (!FrameQueue.empty()) {
FrameQueue_is_ready = true;
Frame = FrameQueue.front();
FrameQueue.pop();
}
else FrameQueue_is_ready = false;
mtx.unlock();
if (FrameQueue_is_ready)
{
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) {
std::cout << "视频编码接受到的帧异常" << std::endl;
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);
}
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;
}