线程之间的等待和唤醒

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空转,避免资源浪费。 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值