一.ffmpeg的推流流程
1. 初始化及配置参数,全局头信息
1.1 初始化FFmpeg的网络模块。
1.2 创建输出上下文,指定H.264编码器。
avformat_alloc_output_context2(&fmt_ctx, nullptr, "rtsp", rtsp_url.c_str())
codec = avcodec_find_encoder(AV_CODEC_ID_H264);
video_stream = avformat_new_stream(fmt_ctx, codec);
codec_ctx = avcodec_alloc_context3(codec);
1.3 配置编码器参数以及格式上下文。
codec_ctx->codec_id = codec->id;
codec_ctx->codec_type = AVMEDIA_TYPE_VIDEO;
codec_ctx->width = width;
codec_ctx->height = height;
codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
codec_ctx->time_base = {1, fps};
codec_ctx->gop_size = 12;
avcodec_open2(codec_ctx, codec, nullptr)
avcodec_parameters_from_context(video_stream->codecpar, codec_ctx);
1.4 全局信息置于流开头,将格式上下文的头信息写入输出流,设置视频流参数
if (fmt_ctx->oformat->flags & AVFMT_GLOBALHEADER) {
codec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
}
av_opt_set(fmt_ctx->priv_data, "rtsp_transport", "tcp", 0);
av_opt_set(fmt_ctx->priv_data, "max_delay", "500000",
0);
avformat_write_header(fmt_ctx, nullptr)
1.5 申请frame用于后续推送,初始化用于颜色格式转换的SWS上下文。
av_frame = av_frame_alloc();
av_frame_get_buffer(av_frame, 32)
sws_ctx_422_to_420 =
sws_getContext(width, height, AV_PIX_FMT_YUYV422,
width, height, AV_PIX_FMT_YUV420P,
SWS_BICUBIC,
nullptr, nullptr, nullptr);
2. 编码及推送一帧数据,计算pts
av_frame->pts =
av_rescale_q(frame_count++, (AVRational){1, 25}, video_stream->time_base);
avcodec_send_frame(codec_ctx, av_frame)
av_init_packet(&pkt);
avcodec_receive_packet(codec_ctx, &pkt)
av_interleaved_write_frame(fmt_ctx, &pkt)
av_packet_unref(&pkt);
二.需要注意的点
- avio_open函数不需要调用,rtmp有flv协议,但是rtsp并没有规定具体协议,在申请输出控制上下文时,ffmpeg将自动创建该io上下文。解答
- 设置PTS,确保与时间基匹配
- AVPacket的释放 av_packet_unref
三.后续改进
四.源码
#ifndef FFMPEGRTSPSTREAMER_H
#define FFMPEGRTSPSTREAMER_H
#pragma once
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/avutil.h>
#include <libavutil/opt.h>
#include <libswscale/swscale.h>
}
#include <cstring>
#include <iostream>
struct YUVFrame {
unsigned char* data;
int width;
int height;
};
class FFmpegRTSPStreamer {
public:
FFmpegRTSPStreamer(const std::string& rtsp_url, int width, int height,
int fps);
~FFmpegRTSPStreamer();
bool init();
bool push_frame(const YUVFrame& frame);
void cleanup();
void YUV422ToYUV420p(const unsigned char* yuv422, unsigned char* yuv420,
int width, int height);
void YUV422ToYUV420pBySWS(const uint8_t* yuv422, uint8_t* yuv420, int width,
int height);
void SaveYUV420pToFile(const uint8_t* yuv420p, int width, int height,
const std::string& filename);
void SaveYUV422pToFile(const uint8_t* yuv422p, int width, int height,
const std::string& filename);
void ConvertYUYVToYUV420P(const unsigned char* yuyv, unsigned char* yuv420p,
int width, int height);
private:
std::string rtsp_url;
int width;
int height;
int fps;
unsigned long long frame_count = 0;
AVFormatContext* fmt_ctx = nullptr;
AVStream* video_stream = nullptr;
AVCodecContext* codec_ctx = nullptr;
AVCodec* codec = nullptr;
AVFrame* av_frame = nullptr;
SwsContext* sws_ctx_422_to_420 = nullptr;
};
#endif
#include "FFmpegRTSPStreamer.h"
#include <fstream>
#include <vector>
FFmpegRTSPStreamer::FFmpegRTSPStreamer(const std::string& rtsp_url, int width,
int height, int fps)
: rtsp_url(rtsp_url),
width(width),
height(height),
fps(fps),
fmt_ctx(nullptr),
video_stream(nullptr),
codec_ctx(nullptr),
codec(nullptr),
av_frame(nullptr) {}
FFmpegRTSPStreamer::~FFmpegRTSPStreamer() { cleanup(); }
bool FFmpegRTSPStreamer::init() {
avformat_network_init();
if (avformat_alloc_output_context2(&fmt_ctx, nullptr, "rtsp",
rtsp_url.c_str()) < 0) {
std::cerr << "Could not allocate output context." << std::endl;
return false;
}
codec = avcodec_find_encoder(AV_CODEC_ID_H264);
if (!codec) {
std::cerr << "H264 codec not found." << std::endl;
return false;
}
video_stream = avformat_new_stream(fmt_ctx, codec);
if (!video_stream) {
std::cerr << "Failed to create video stream." << std::endl;
return false;
}
codec_ctx = avcodec_alloc_context3(codec);
if (!codec_ctx) {
std::cerr << "Failed to allocate codec context." << std::endl;
return false;
}
codec_ctx->codec_id = codec->id;
codec_ctx->codec_type = AVMEDIA_TYPE_VIDEO;
codec_ctx->width = width;
codec_ctx->height = height;
codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
codec_ctx->time_base = {1, fps};
codec_ctx->gop_size = 12;
if (fmt_ctx->oformat->flags & AVFMT_GLOBALHEADER) {
codec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
}
if (avcodec_open2(codec_ctx, codec, nullptr) < 0) {
std::cerr << "Failed to open codec." << std::endl;
return false;
}
avcodec_parameters_from_context(video_stream->codecpar, codec_ctx);
av_opt_set(fmt_ctx->priv_data, "rtsp_transport", "tcp", 0);
av_opt_set(fmt_ctx->priv_data, "max_delay", "500000",
0);
if (avformat_write_header(fmt_ctx, nullptr) < 0) {
std::cerr << "Error occurred when writing header." << std::endl;
return false;
}
av_frame = av_frame_alloc();
av_frame->format = codec_ctx->pix_fmt;
av_frame->width = width;
av_frame->height = height;
if (av_frame_get_buffer(av_frame, 32) < 0) {
std::cerr << "Could not allocate frame buffer." << std::endl;
return false;
}
sws_ctx_422_to_420 =
sws_getContext(width, height, AV_PIX_FMT_YUYV422,
width, height, AV_PIX_FMT_YUV420P,
SWS_BICUBIC,
nullptr, nullptr, nullptr);
if (!sws_ctx_422_to_420) {
std::cerr << "Error initializing the conversion context sws_ctx_422_to_420."
<< std::endl;
return false;
}
return true;
}
bool FFmpegRTSPStreamer::push_frame(const YUVFrame& frame) {
if (!av_frame || !frame.data) {
std::cerr << "Invalid frame or uninitialized streamer." << std::endl;
return false;
}
memcpy(av_frame->data[0], frame.data, width * height);
memcpy(av_frame->data[1], frame.data + width * height,
width * height / 4);
memcpy(av_frame->data[2], frame.data + width * height * 5 / 4,
width * height / 4);
av_frame->pts =
av_rescale_q(frame_count++, (AVRational){1, 25}, video_stream->time_base);
if (avcodec_send_frame(codec_ctx, av_frame) < 0) {
std::cerr << "Failed to send frame to "
"encoder."
<< std::endl;
return false;
}
AVPacket pkt;
av_init_packet(&pkt);
pkt.data = nullptr;
pkt.size = 0;
if (avcodec_receive_packet(codec_ctx, &pkt) == 0) {
pkt.stream_index = video_stream->index;
std::cout << "frame_count:" << frame_count << " PTS: " << pkt.pts
<< " DTS: " << pkt.dts << " size: " << pkt.size << std::endl;
if (av_interleaved_write_frame(fmt_ctx, &pkt) < 0) {
std::cerr << "Failed to write frame. PTS: " << pkt.pts
<< " DTS: " << pkt.dts << " size: " << pkt.size << std::endl;
av_packet_unref(&pkt);
return false;
}
av_packet_unref(&pkt);
}
return true;
}
void FFmpegRTSPStreamer::cleanup() {
if (fmt_ctx) {
av_write_trailer(fmt_ctx);
if (fmt_ctx->pb) {
avio_close(fmt_ctx->pb);
}
avformat_free_context(fmt_ctx);
fmt_ctx = nullptr;
}
if (codec_ctx) {
avcodec_free_context(&codec_ctx);
codec_ctx = nullptr;
}
if (av_frame) {
av_frame_free(&av_frame);
av_frame = nullptr;
}
if (sws_ctx_422_to_420) {
sws_freeContext(sws_ctx_422_to_420);
sws_ctx_422_to_420 = nullptr;
}
}
void FFmpegRTSPStreamer::YUV422ToYUV420p(const unsigned char* yuv422,
unsigned char* yuv420, int width,
int height) {
int y_size = width * height;
int uv_size = width * height / 4;
const unsigned char* y_data = yuv422;
const unsigned char* u_data = yuv422 + y_size;
const unsigned char* v_data =
u_data + (width * height) / 2;
unsigned char* y_out = yuv420;
unsigned char* u_out = yuv420 + y_size;
unsigned char* v_out = u_out + uv_size;
memcpy(y_out, y_data, y_size);
for (int i = 0; i < height / 2; i++) {
for (int j = 0; j < width / 2; j++) {
int index422 = 2 * (i * width + j);
int index420_u = i * (width / 2) + j;
int index420_v = index420_u;
u_out[index420_u] = (u_data[index422] + u_data[index422 + 1]) / 2;
v_out[index420_v] = (v_data[index422] + v_data[index422 + 1]) / 2;
}
}
}
void FFmpegRTSPStreamer::YUV422ToYUV420pBySWS(const uint8_t* yuv422,
uint8_t* yuv420, int width,
int height) {
uint8_t* src_data[4] = {const_cast<uint8_t*>(yuv422), nullptr, nullptr,
nullptr};
int src_linesize[4] = {width, width / 2, width / 2, 0};
uint8_t* dst_data[4] = {yuv420, nullptr, nullptr, nullptr};
int dst_linesize[4] = {width, width / 2, width / 2, 0};
int ret = sws_scale(sws_ctx_422_to_420,
src_data,
src_linesize,
0,
height,
dst_data,
dst_linesize
);
if (ret < 0) {
std::cerr << "Error during conversion." << std::endl;
}
}
void FFmpegRTSPStreamer::SaveYUV420pToFile(const uint8_t* yuv420p, int width,
int height,
const std::string& filename) {
std::ofstream file(filename, std::ios::binary);
if (!file) {
std::cerr << "无法打开文件:" << filename << std::endl;
return;
}
int y_size = width * height;
int uv_size =
width * height / 4;
file.write(reinterpret_cast<const char*>(yuv420p), y_size);
file.write(reinterpret_cast<const char*>(yuv420p + y_size),
uv_size);
file.write(reinterpret_cast<const char*>(yuv420p + y_size + uv_size),
uv_size);
file.close();
std::cout << "YUV 数据已保存为 " << filename << std::endl;
}
void FFmpegRTSPStreamer::SaveYUV422pToFile(const uint8_t* yuv422p, int width,
int height,
const std::string& filename) {
std::ofstream file(filename, std::ios::binary);
if (!file) {
std::cerr << "无法打开文件:" << filename << std::endl;
return;
}
int y_size = width * height;
int uv_size = width * height / 2;
file.write(reinterpret_cast<const char*>(yuv422p), y_size);
file.write(reinterpret_cast<const char*>(yuv422p + y_size),
uv_size);
file.write(reinterpret_cast<const char*>(yuv422p + y_size + uv_size),
uv_size);
file.close();
std::cout << "YUV 数据已保存为 " << filename << std::endl;
}
void FFmpegRTSPStreamer::ConvertYUYVToYUV420P(const unsigned char* yuyv,
unsigned char* yuv420p, int width,
int height) {
int frameSize = width * height;
unsigned char* yPlane = yuv420p;
unsigned char* uPlane = yuv420p + frameSize;
unsigned char* vPlane = yuv420p + frameSize * 5 / 4;
memset(yPlane, 0, frameSize);
memset(uPlane, 0, frameSize / 4);
memset(vPlane, 0, frameSize / 4);
for (int j = 0; j < height; j++) {
for (int i = 0; i < width; i += 2) {
int yuyvIndex = (j * width + i) * 2;
unsigned char y1 = yuyv[yuyvIndex];
unsigned char u = yuyv[yuyvIndex + 1];
unsigned char y2 = yuyv[yuyvIndex + 2];
unsigned char v = yuyv[yuyvIndex + 3];
yPlane[j * width + i] = y1;
yPlane[j * width + i + 1] = y2;
if (j % 2 == 0 && i % 2 == 0) {
int uvIndex = (j / 2) * (width / 2) + (i / 2);
uPlane[uvIndex] = u;
vPlane[uvIndex] = v;
}
}
}
}