FFmpeg中一个线程获取视频流一个线程执行scale测试代码

本文介绍了一种双线程优化方案,用于提升USB视频流在高分辨率下的实时显示性能。通过将av_read_frame和sws_scale两个关键函数分别放在独立线程中并行执行,有效解决了串行执行导致的卡顿问题。测试代码展示了具体的实现细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

https://blog.youkuaiyun.com/fengbingchun/article/details/94712986 中介绍过如果usb视频流编码类型为rawvideo则无需进行解码,可直接通过av_read_frame获取视频流然后再通过sws_scale进行像素格式转换,当在高分辨率情况下,有时达不到实时显示,会有卡顿,经分析,性能瓶颈在av_read_frame和sws_scale,由于在https://blog.youkuaiyun.com/fengbingchun/article/details/94712986中将此两个函数放在一个线程中串行执行因此导致卡顿。这里将两个函数放在两个独立的线程中并行执行,一个线程负责执行av_read_frame,另一个线程负责执行sws_scale,性能得到大幅提升。

测试代码如下:

common.hpp:

#ifndef FBC_FFMPEG_TEST_COMMON_HPP_
#define FBC_FFMPEG_TEST_COMMON_HPP_

#include <chrono>
#include <queue>
#include <mutex>
#include <condition_variable>

class Timer {
public:
	static long long getNowTime() { // milliseconds
		auto now = std::chrono::system_clock::now();
		return std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count();
	}
};

typedef struct Buffer {
	unsigned char* data;
	unsigned int length;
} Buffer;

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

	void push(Buffer& buffer) {
		std::unique_lock<std::mutex> lck(mtx);
		queue.push(buffer);
		cv.notify_all();
	}

	void pop(Buffer& buffer) {
		std::unique_lock<std::mutex> lck(mtx);
		while (queue.empty()) {
			cv.wait(lck);
		}
		buffer = queue.front();
		queue.pop();
	}

	unsigned int size() {
		return queue.size();
	}

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

class PacketScaleQueue {
public:
	PacketScaleQueue() = default;

	~PacketScaleQueue() {
		Buffer buffer;

		while (getPacketSize() > 0) {
			popPacket(buffer);
			delete[] buffer.data;
		}

		while (getScaleSize() > 0) {
			popScale(buffer);
			delete[] buffer.data;
		}
	}

	void init(unsigned int buffer_num = 16, unsigned int buffer_size = 1024 * 1024 * 4) {
		for (unsigned int i = 0; i < buffer_num; ++i) {
			Buffer buffer = { new unsigned char[buffer_size], buffer_num};
			pushPacket(buffer);
		}
	}

	void pushPacket(Buffer& buffer) { packet_queue.push(buffer); }
	void popPacket(Buffer& buffer) { packet_queue.pop(buffer); }
	unsigned int getPacketSize() { return packet_queue.size(); }

	void pushScale(Buffer& buffer) { scale_queue.push(buffer); }
	void popScale(Buffer& buffer) { scale_queue.pop(buffer); }
	unsigned int getScaleSize() { return scale_queue.size(); }

private:
	BufferQueue packet_queue, scale_queue;
};

#endif // FBC_FFMPEG_TEST_COMMON_HPP_

test_ffmpeg_decode_show.cpp:

#include "funset.hpp"
#include <stdio.h>
#include <string.h>
#include <iostream>
#include <memory>
#include <fstream>
#include <thread>
#include "common.hpp"

#ifdef __cplusplus
extern "C" {
#endif

#include <libavdevice/avdevice.h>
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>
#include <libavutil/mem.h>
#include <libavutil/imgutils.h>

#ifdef __cplusplus
}
#endif

#include <opencv2/opencv.hpp>

namespace {

bool packet_scale_flag = true;

void get_packet(AVFormatContext* format_ctx, int video_stream_index, PacketScaleQueue& packet_scale)
{
	//for (int i = 0; i < 100; ++i) {
	while (packet_scale_flag) {
		AVPacket packet;
		//long long t1 = Timer::getNowTime();
		int ret = av_read_frame(format_ctx, &packet);
		//long long t2 = Timer::getNowTime();
		//fprintf(stdout, "av_read frame cost time: %lldms\n", t2 - t1);
		if (ret >= 0 && packet.stream_index == video_stream_index && packet.size > 0) {
			Buffer buffer;
			packet_scale.popPacket(buffer);
			memcpy(buffer.data, packet.data, packet.size);
			packet_scale.pushScale(buffer);
			av_packet_unref(&packet);
		} else {
			fprintf(stderr, "##### fail to av_read_frame: %d, %d\n", ret, packet.size);
		}
	}
}

void get_scale(AVCodecContext* codec_ctx, PacketScaleQueue& packet_scale)
{
	SwsContext* sws_ctx = sws_getContext(codec_ctx->width, codec_ctx->height, codec_ctx->pix_fmt, codec_ctx->width, codec_ctx->height, AV_PIX_FMT_BGR24, 0, nullptr, nullptr, nullptr);
	if (!sws_ctx) {
		fprintf(stderr, "##### fail to sws_getContext\n");
	}

	uint8_t *bgr_data[4], *yuyv422_data[4];
	int bgr_linesize[4], yuyv422_linesize[4];
	av_image_alloc(bgr_data, bgr_linesize, codec_ctx->width, codec_ctx->height, AV_PIX_FMT_BGR24, 1);
	av_image_alloc(yuyv422_data, yuyv422_linesize, codec_ctx->width, codec_ctx->height, AV_PIX_FMT_YUYV422, 1);

	cv::Mat mat(codec_ctx->height, codec_ctx->width, CV_8UC3);
	const char* winname = "usb video";
	cv::namedWindow(winname);

	//for (int i = 0; i < 100; ++i) {
	while (packet_scale_flag) {
		Buffer buffer;
		packet_scale.popScale(buffer);
		const uint8_t *srcSlice[1];
		srcSlice[0] = buffer.data;
		//long long t1 = Timer::getNowTime();
		sws_scale(sws_ctx, srcSlice, yuyv422_linesize, 0, codec_ctx->height, bgr_data, bgr_linesize);
		//long long t2 = Timer::getNowTime();
		//fprintf(stdout, "sws_scale cost time: %lldms\n", t2 - t1);
		packet_scale.pushPacket(buffer);

		mat.data = bgr_data[0];
		cv::imshow(winname, mat);
		//cv::imwrite("xxx.jpg", mat);

		int key = cv::waitKey(10);
		if (key == 27) { 
			packet_scale_flag = false;
			break;
		}
	}

	cv::destroyWindow(winname);
	sws_freeContext(sws_ctx);
	av_freep(&bgr_data[0]);
	av_freep(&yuyv422_data[0]);
}

} // namespace

int test_ffmpeg_stream_show_two_thread()
{
	avdevice_register_all();

#ifdef _MSC_VER
	const char* input_format_name = "vfwcap";
	const char* url = "";
#else
	const char* input_format_name = "video4linux2";
	const char* url = "/dev/video0";
#endif

	AVInputFormat* input_fmt = av_find_input_format(input_format_name);
	AVFormatContext* format_ctx = avformat_alloc_context();

	int ret = avformat_open_input(&format_ctx, url, input_fmt, nullptr);
	if (ret != 0) {
		fprintf(stderr, "fail to open url: %s, return value: %d\n", url, ret);
		return -1;
	}

	ret = avformat_find_stream_info(format_ctx, nullptr);
	if (ret < 0) {
		fprintf(stderr, "fail to get stream information: %d\n", ret);
		return -1;
	}

	int video_stream_index = -1;
	for (unsigned int i = 0; i < format_ctx->nb_streams; ++i) {
		const AVStream* stream = format_ctx->streams[i];
		if (stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
			video_stream_index = i;
			fprintf(stdout, "type of the encoded data: %d, dimensions of the video frame in pixels: width: %d, height: %d, pixel format: %d\n",
				stream->codecpar->codec_id, stream->codecpar->width, stream->codecpar->height, stream->codecpar->format);
		}
	}

	if (video_stream_index == -1) {
		fprintf(stderr, "no video stream\n");
		return -1;
	}

	AVCodecParameters* codecpar = format_ctx->streams[video_stream_index]->codecpar;
	const AVCodec* codec = avcodec_find_decoder(codecpar->codec_id);
	if (!codec) {
		fprintf(stderr, "fail to avcodec_find_decoder\n");
		return -1;
	}

	if (codecpar->codec_id != AV_CODEC_ID_RAWVIDEO) {
		fprintf(stderr, "this test code only support rawvideo encode: %d\n", codecpar->codec_id);
		return -1;
	}

	AVCodecContext* codec_ctx = avcodec_alloc_context3(codec);
	if (!codec_ctx) {
		fprintf(stderr, "fail to avcodec_alloc_context3\n");
		return -1;
	}

	codec_ctx->pix_fmt = AVPixelFormat(codecpar->format);
	codec_ctx->height = codecpar->height;
	codec_ctx->width = codecpar->width;
	codec_ctx->thread_count = 16;
	ret = avcodec_open2(codec_ctx, codec, nullptr);
	if (ret != 0) {
		fprintf(stderr, "fail to avcodec_open2: %d\n", ret);
		return -1;
	}

	PacketScaleQueue packet_scale;
	packet_scale.init(16, 1024*1024*4);

	std::thread thread_packet(get_packet, format_ctx, video_stream_index, std::ref(packet_scale));
	std::thread thread_scale(get_scale, codec_ctx, std::ref(packet_scale));

	thread_packet.join();
	thread_scale.join();

	avformat_close_input(&format_ctx);

	fprintf(stdout, "test finish\n");
	return 0;
}

执行结果如下:

GitHubhttps://github.com/fengbingchun/OpenCV_Test

<think>好的,用户现在想要了解如何使用FFmpeg一条命令推送多个视频流。我需要先回顾之前的对话,确保这次回答符合用户的需求。之前用户询问的是SpringBoot结合FFmpeg和SRS推送多条RTMP流,现在的问题更具体,专注于FFmpeg本身的命令实现多流推送。 首先,用户可能是在实际应用中需要同时向多个平台或服务器推流,比如同时推送到YouTube、Twitch和自己的SRS服务器。这种情况下,手动多次执行FFmpeg命令效率低下,占用资源多,所以用户希望一条命令解决。 接下来,我需要回忆FFmpeg中如何处理多输出流。FFmpeg支持使用多个输出参数,即在一个命令中指定多个输出地址。每个输出需要独立的编码参数和格式设置。可能需要用到-map选项来指定不同的流映射,或者复制现有的流而不重新编码,以节省资源。 用户可能的疑问点包括:如何正确编写命令结构,如何复制流以避免重复编码,如何处理不同的编码格式要求,以及如何管理多个输出地址。同时,用户可能关心资源占用问题,比如CPU和带宽的使用,特别是当推流数量较多时。 另外,用户可能需要了解一些实际示例,例如同时推送到RTMP服务器和保存本地文件,或者调整不同输出的分辨率和码率。还需要注意常见的错误,比如地址格式错误、编码参数冲突,以及如何监控推流状态。 需要确保回答的结构清晰,分步骤说明,并提供具体的命令示例,帮助用户理解。同时,强调资源管理和错误处理,因为多流推送对系统负载较高,合理的参数设置能有效优化性能。 最后,检查是否有遗漏的部分,比如是否需要使用过滤器链处理不同的视频流,或者如何管理音频流的处理。确保覆盖用户可能遇到的各种情况,并提供优化建议,如使用硬件加速、调整线程数等。</think>以下是使用 FFmpeg 单条命令推送多个视频流的完整实现方案: --- ### 一、核心原理 $$FFmpeg \xrightarrow{\text{单输入多输出}} \begin{cases} rtmp://server1/live/stream1 \\ rtmp://server2/live/stream2 \\ file.mp4 \end{cases}$$ 1. 利用 FFmpeg 的 **多输出参数** 特性 2. 支持 **相同内容多路推送** 或 **不同编码规格分流** --- ### 二、基础命令结构 ```bash ffmpeg -i input.mp4 \ [输出流1参数] rtmp://url1 \ [输出流2参数] rtmp://url2 \ [输出流3参数] local_file.mkv ``` --- ### 三、典型场景实现 #### 场景1:相同内容推送到多个RTMP服务器(不转码) ```bash ffmpeg -re -i input.mp4 \ -c:v copy -c:a copy -f flv "rtmp://srs1.example.com/live/stream1" \ -c:v copy -c:a copy -f flv "rtmp://srs2.example.com/live/stream2" ``` $$参数解析:$$ - `-c:v copy`:视频流直接复制(零CPU消耗) - `-f flv`:强制指定输出容器格式为FLV - 每个输出地址需要独立参数组 #### 场景2:不同规格转码后推送(H264+HEVC双路) ```bash ffmpeg -re -i input.mp4 \ -map 0:v:0 -map 0:a:0 \ -c:v libx264 -b:v 3M -preset fast \ -c:a aac -b:a 128k \ -f flv "rtmp://server/live/h264_stream" \ -map 0:v:0 -map 0:a:0 \ -c:v libx265 -b:v 2M -preset medium \ -c:a copy \ -f flv "rtmp://server/live/hevc_stream" ``` $$关键技术点:$$ 1. `-map 0:v:0` 显式指定输入流的索引 2. 为每个输出独立设置编码参数 --- ### 四、高级用法 #### 1. 多分辨率自适应推流 ```bash ffmpeg -re -i input.mp4 \ -filter_complex \ "[0:v]split=3[v1][v2][v3]; \ [v1]scale=1280:720[vout1]; \ [v2]scale=854:480[vout2]; \ [v3]scale=640:360[vout3]" \ -map "[vout1]" -map 0:a -c:v libx264 -b:v 2500k -f flv rtmp://url/720p \ -map "[vout2]" -map 0:a -c:v libx264 -b:v 1500k -f flv rtmp://url/480p \ -map "[vout3]" -map 0:a -c:v libx264 -b:v 800k -f flv rtmp://url/360p ``` $$滤波器解析:$$ - `split=3`
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值