FFmpeg支持多线程编码并保存mp4文件示例

news/2024/7/10 21:13:33 标签: ffmpeg

      之前介绍的示例:

      (1).https://blog.csdn.net/fengbingchun/article/details/132129988 中对编码后数据保存成mp4

      (2).https://blog.csdn.net/fengbingchun/article/details/132128885 中通过AVIOContext实现从内存读取数据

      (3).https://blog.csdn.net/fengbingchun/article/details/132389734 中将图像加载到视频中

      这里将三部分整合到类中,便于后面增加测试代码,下面的示例是两个线程:从内存中读取数据,并将指定的图像加载到视频,将结果保存成mp4。

      示例代码如下:

      1. 类PacketScaleQueue:用于持续的从指定内存中读取原始数据,上面的示例中已包含此代码

      2.类CodecQueue:用于将解码数据存入队列中,并通过单独的线程进行编码

class AVFrameQueue {
public:
	AVFrameQueue() = default;
	~AVFrameQueue() {}

	void push(AVFrame** frame) {
		std::unique_lock<std::mutex> lck(mtx);
		queue.push(*frame);
		cv.notify_all();
	}

	void pop(AVFrame** frame) {
		std::unique_lock<std::mutex> lck(mtx);
		while (queue.empty()) {
			//cv.wait(lck);
			if (cv.wait_for(lck, std::chrono::milliseconds(150)) == std::cv_status::timeout) {
				fprintf(stderr, "#### Warning: wait timeout\n");
				*frame = nullptr;
				return;
			}
		}
		*frame = queue.front();
		queue.pop();
	}

	size_t size() const {
		return queue.size();
	}

private:
	std::queue<AVFrame*> queue;
	std::mutex mtx;
	std::condition_variable cv;
};

class CodecQueue {
public:
	CodecQueue() = default;
	void init(unsigned int frame_num) {
		for (auto i = 0; i < frame_num; ++i) {
			AVFrame* frame = nullptr;
			pushDecode(&frame);
		}
	}

	~CodecQueue() { release(); }

	void release() {
		AVFrame* frame = nullptr;

		while (getDecodeSize() > 0) {
			popDecode(&frame);
			av_frame_free(&frame);
		}

		while (getEncodeSize() > 0) {
			popEncode(&frame);
			av_frame_free(&frame);
		}
	}

	void pushDecode(AVFrame** frame) { decode_queue.push(frame); }
	void popDecode(AVFrame** frame) { decode_queue.pop(frame); }
	size_t getDecodeSize() const { return decode_queue.size(); }

	void pushEncode(AVFrame** frame) { encode_queue.push(frame); }
	void popEncode(AVFrame** frame) { encode_queue.pop(frame); }
	size_t getEncodeSize() const { return encode_queue.size(); }

private:
	AVFrameQueue decode_queue, encode_queue;
};

      3.类VideoCodec:供外面的接口调用,封装了视频的解码和编码过程

      声明如下:

typedef struct CodecCtx {
	char outfile_name[VIDEO_CODEC_MAX_STRING_SIZE];
	char video_size[VIDEO_CODEC_MAX_STRING_SIZE];
	char bitrate_str[VIDEO_CODEC_MAX_STRING_SIZE];
	char pixel_format[VIDEO_CODEC_MAX_STRING_SIZE];
	char filter_descr[VIDEO_CODEC_MAX_STRING_SIZE];
	AVFormatContext* ifmt_ctx;
	AVFormatContext* ofmt_ctx;
	AVCodecContext* dec_ctx;
	AVCodecContext* enc_ctx;
	AVFrame* dec_frame;
	AVFilterContext* buffersink_ctx;
	AVFilterContext* buffersrc_ctx;
	AVFilterGraph* filter_graph;
	AVPacket* enc_pkt;
	AVRational frame_rate;
	int term_status;
	int stream_index;
	int frame_count;
	bool encode_thread_end;
} CodecCtx;

class VideoCodec {
public:
	VideoCodec() = default;
	~VideoCodec() {  }

	void setOutfileName(const std::string& name) { outfile_name_ = name; }
	void setVideoSize(const std::string& size) { video_size_ = size; }
	void setPixelFormat(const std::string& format) { pixel_format_ = format; }
	void setFilterDescr(const std::string& filter_descr) { filter_descr_ = filter_descr; }

	void stopEncode() {
		while (raw_packet_queue_.getScaleSize() != 0);

		codec_ctx_->term_status = 1;

		Buffer buffer;
		raw_packet_queue_.popPacket(buffer);
		memset(buffer.data, 0, block_size_);
		raw_packet_queue_.pushScale(buffer); // for av_read_frame to exit normally
	}

	PacketScaleQueue& get_raw_packet_queue(unsigned int buffer_num, size_t buffer_size) {
		raw_packet_queue_.init(buffer_num, buffer_size);
		block_size_ = buffer_size;
		return raw_packet_queue_;
	}

	int openEncode();
	int processEncode();
	int closeEncode();

private:
	std::string outfile_name_ = "";
	std::string video_size_ = "";
	std::string pixel_format_ = "";
	std::string filter_descr_ = "";
	PacketScaleQueue raw_packet_queue_;
	int block_size_ = 0;
	CodecCtx* codec_ctx_ = nullptr;
	AVIOContext* avio_ctx_ = nullptr;
	CodecQueue codec_queue_;
	std::thread encode_thread_;

	int get_decode_context();
	int get_encode_context();
	int init_filters();
	int filter_encode_write_frame(AVFrame* frame);
	int get_output_format_context();

	int flush_encode_write_frame();
	int flush_decoder();
	int flush_encoder();
	void flush_codec();
};

      类VideoCodec实现部分:是之前示例的整理,参考之前示例

      4.测试代码,即调用VideoCodec接口,以下是同时两个线程进行编码写

namespace {

const int total_push_count = 121;
bool flag1 = true;
const size_t block_size_1 = 640 * 480 * 3;
size_t total_push_count_1 = 0;

void fill_raw_data_1(PacketScaleQueue& raw_packet)
{
    unsigned char value = 0;
    while (total_push_count_1 < total_push_count) {
        value += 10;
        if (value >= 255) value = 0;

        Buffer buffer;
        raw_packet.popPacket(buffer);
        memset(buffer.data, value, block_size_1);
        raw_packet.pushScale(buffer);

        std::this_thread::sleep_for(std::chrono::milliseconds(33));
        ++total_push_count_1;
    }

    flag1 = false;
}

void sleep_seconds_1(VideoCodec& video_codec)
{
    while (flag1) {
        std::this_thread::sleep_for(std::chrono::milliseconds(33));
    }

    video_codec.stopEncode();
}

void encode_1()
{
    VideoCodec video_codec;
    video_codec.setOutfileName("out1.mp4");
    video_codec.setVideoSize("640x480");
    video_codec.setPixelFormat("bgr24");
    video_codec.setFilterDescr("movie=1.jpg[logo];[in][logo]overlay=10:20[out]");

    auto& raw_queue = video_codec.get_raw_packet_queue(16, block_size_1);
    std::thread thread_fill(fill_raw_data_1, std::ref(raw_queue));

    auto ret = video_codec.openEncode();
    if (ret != 0) {
        std::cout << "fail to openEncode: " << ret << std::endl;
        //return -1;
    }

    std::thread thread_sleep(sleep_seconds_1, std::ref(video_codec));

    ret = video_codec.processEncode();
    if (ret != 0) {
        std::cout << "fail to processEncode: " << ret << std::endl;
        //return -1;
    }

    thread_fill.join();
    thread_sleep.join();

    video_codec.closeEncode();

    std::cout << "1 total push count: " << total_push_count_1 << std::endl;
}

bool flag2 = true;
const size_t block_size_2 = 640 * 480 * 3;
size_t total_push_count_2 = 0;

void fill_raw_data_2(PacketScaleQueue& raw_packet)
{
    unsigned char value = 0;
    while (total_push_count_2 < total_push_count) {
        value += 10;
        if (value >= 255) value = 0;

        Buffer buffer;
        raw_packet.popPacket(buffer);
        memset(buffer.data, value, block_size_2);
        raw_packet.pushScale(buffer);

        std::this_thread::sleep_for(std::chrono::milliseconds(33));
        ++total_push_count_2;
    }

    flag2 = false;
}

void sleep_seconds_2(VideoCodec& video_codec)
{
    while (flag2) {
        std::this_thread::sleep_for(std::chrono::milliseconds(33));
    }

    video_codec.stopEncode();
}

void encode_2()
{
    VideoCodec video_codec;
    video_codec.setOutfileName("out2.mp4");
    video_codec.setVideoSize("640x480");
    video_codec.setPixelFormat("bgr24");
    video_codec.setFilterDescr("movie=1.jpg[logo];[in][logo]overlay=10:20[out]");

    auto& raw_queue = video_codec.get_raw_packet_queue(16, block_size_2);
    std::thread thread_fill(fill_raw_data_2, std::ref(raw_queue));

    auto ret = video_codec.openEncode();
    if (ret != 0) {
        std::cout << "fail to openEncode: " << ret << std::endl;
        //return -1;
    }

    std::thread thread_sleep(sleep_seconds_2, std::ref(video_codec));

    ret = video_codec.processEncode();
    if (ret != 0) {
        std::cout << "fail to processEncode: " << ret << std::endl;
        //return -1;
    }

    thread_fill.join();
    thread_sleep.join();

    std::cout << "2 total push count: " << total_push_count_2 << std::endl;
}

} // namespce

int test_ffmpeg_libavfilter_movie_multi_thread()
{
    std::thread thread_1(encode_1);
    std::thread thread_2(encode_2);

    thread_1.join();
    thread_2.join();

    std::cout << "test finish" << std::endl;
    return 0;
}

      生成的mp4文件结果如下:在release下生成的两个视频文件完全一致;在debug下编码过程中有时会timeout

      GitHub:https://github.com/fengbingchun/OpenCV_Test


http://www.niftyadmin.cn/n/4976046.html

相关文章

BUUCTF [SWPU2019]Web1

​ 这是一道sql二次注入题目&#xff0c;但是注入点并不在登录处 注册一个用户然后登录 广告申请处进行sql注入 你会发现过滤了很多关键字 空格#information等等 这里用到了一些绕过技巧 使用 /**/ 代替空格 union/**/select/**/1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,1…

Zookeeper 脑裂问题

什么是脑裂&#xff1f; 脑裂(split-brain)就是“大脑分裂”&#xff0c;也就是本来一个“大脑”被拆分了两个或多个“大脑”&#xff0c;如果一个人有多个大脑&#xff0c;并且相互独立的话&#xff0c;那么会导致人体“手舞足蹈”&#xff0c;“不听使唤”。 脑裂通常会出现…

postgresql类型转换函数

postgresql类型转换函数 简介CAST 函数to_date 函数to_timestamp 函数to_char 函数to_number 函数隐式类型转换 简介 类型转换函数用于将数据从一种类型转换为另一种类型。 CAST 函数 CAST ( expr AS data_type )函数用于将 expr 转换为 data_type 数据类型&#xff1b;Post…

Adapter Tuning Overview:在CV,NLP,多模态领域的代表性工作

文章目录 Delta TuningAdapter Tuning in CVAdapter Tuning in NLP Delta Tuning Adapter Tuning in CV 题目: Learning multiple visual domains with residual adapters 机构&#xff1a;牛津VGG组 论文: https://arxiv.org/pdf/1705.08045.pdf Adapter Tuning in NLP …

在 WSL2 中使用 NVIDIA Docker 进行全栈开发和深度学习 TensorFlow pytorch GPU 加速

WSL2使用NVIDIA Docker进行全栈开发和深度学习 1. 前置条件 1.1. 安装系统 Windows 10 版本 2004 及更高版本&#xff08;内部版本 19041 及更高版本&#xff09;或 Windows 11 跳过 1.2. 处理好网络环境 安装过程中需要访问国际网络&#xff0c;自行处理好。建议开启 tu…

python实例方法,类方法和静态方法区别

为python中的装饰器 实例方法 实例方法时直接定义在类中的函数&#xff0c;不需要任何修饰。只能通过类的实例化对象来调用。不能通过类名来调用。 类方法 类方法&#xff0c;是类中使用classmethod修饰的函数。类方法在定义的时候需要有表示类对象的参数(一般命名为cls&#…

力扣-数组-缺失的第一个正整数

题目 给你一个未排序的整数数组 nums &#xff0c;请你找出其中没有出现的最小的正整数。 请你实现时间复杂度为 O(n) 并且只使用常数级别额外空间的解决方案。 示例 1&#xff1a; **输入&#xff1a;**nums [1,2,0] **输出&#xff1a;**3 示例 2&#xff1a; **输入&…

Mycat之前世今生

如果我有一个32核心的服务器&#xff0c;我就可以实现1个亿的数据分片&#xff0c;我有32核心的服务器么&#xff1f;没有&#xff0c;所以我至今无法实现1个亿的数据分片。——MyCAT ‘s Plan 话说“每一个成功的男人背后都有一个女人”&#xff0c;自然MyCAT也逃脱不了这个诅…