使用 FFmpeg.AutoGen 将 h264 视频流通过 RTMP 推流
以下是使用 FFmpeg.AutoGen 将 h264 视频流通过 RTMP 推流的 C# 示例代码:
using FFmpeg.AutoGen;
using System;
using System.Runtime.InteropServices;
namespace RtmpPusher
{
class Program
{
static void Main(string[] args)
{
// 初始化 FFmpeg
ffmpeg.av_register_all();
ffmpeg.avformat_network_init();
// 打开输入文件
AVInputFormat* inputFormat = ffmpeg.av_find_input_format('video4linux2');
AVFormatContext* inputContext = null;
if (ffmpeg.avformat_open_input(&inputContext, '/dev/video0', inputFormat, null) != 0)
{
Console.WriteLine('Could not open input file.');
return;
}
// 查找视频流
AVCodec* videoCodec = null;
int videoStreamIndex = ffmpeg.av_find_best_stream(inputContext, AVMediaType.AVMEDIA_TYPE_VIDEO, -1, -1, &videoCodec, 0);
if (videoStreamIndex < 0)
{
Console.WriteLine('Could not find video stream.');
return;
}
// 打开视频解码器
AVCodecContext* videoCodecContext = inputContext->streams[videoStreamIndex]->codec;
if (ffmpeg.avcodec_open2(videoCodecContext, videoCodec, null) != 0)
{
Console.WriteLine('Could not open video codec.');
return;
}
// 初始化输出上下文
AVFormatContext* outputContext = null;
if (ffmpeg.avformat_alloc_output_context2(&outputContext, null, 'flv', 'rtmp://localhost/live') != 0)
{
Console.WriteLine('Could not allocate output context.');
return;
}
// 添加视频流到输出上下文
AVStream* videoStream = ffmpeg.avformat_new_stream(outputContext, null);
if (videoStream == null)
{
Console.WriteLine('Could not create video stream.');
return;
}
videoStream->codecpar->codec_type = AVMediaType.AVMEDIA_TYPE_VIDEO;
videoStream->codecpar->codec_id = videoCodecContext->codec_id;
videoStream->codecpar->width = videoCodecContext->width;
videoStream->codecpar->height = videoCodecContext->height;
videoStream->codecpar->format = (int)AVPixelFormat.AV_PIX_FMT_YUV420P;
videoStream->time_base = videoCodecContext->time_base;
// 打开视频编码器
AVCodec* outputVideoCodec = ffmpeg.avcodec_find_encoder(AVCodecID.AV_CODEC_ID_H264);
AVCodecContext* outputVideoCodecContext = ffmpeg.avcodec_alloc_context3(outputVideoCodec);
ffmpeg.avcodec_parameters_to_context(outputVideoCodecContext, videoStream->codecpar);
if (ffmpeg.avcodec_open2(outputVideoCodecContext, outputVideoCodec, null) != 0)
{
Console.WriteLine('Could not open video encoder.');
return;
}
// 初始化输出文件
if ((outputContext->oformat->flags & ffmpeg.AVFMT_NOFILE) == 0 && ffmpeg.avio_open2(&outputContext->pb, 'rtmp://localhost/live', ffmpeg.AVIO_FLAG_WRITE, null, null) != 0)
{
Console.WriteLine('Could not open output file.');
return;
}
// 写入输出文件头
if (ffmpeg.avformat_write_header(outputContext, null) != 0)
{
Console.WriteLine('Could not write output file header.');
return;
}
// 分配 AVPacket 和 AVFrame
AVPacket* packet = ffmpeg.av_packet_alloc();
AVFrame* frame = ffmpeg.av_frame_alloc();
byte[] buffer = new byte[videoCodecContext->width * videoCodecContext->height * 3 / 2];
GCHandle handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
IntPtr bufferPtr = handle.AddrOfPinnedObject();
frame->data[0] = bufferPtr;
frame->data[1] = bufferPtr + videoCodecContext->width * videoCodecContext->height;
frame->data[2] = bufferPtr + videoCodecContext->width * videoCodecContext->height * 5 / 4;
frame->linesize[0] = videoCodecContext->width;
frame->linesize[1] = videoCodecContext->width / 2;
frame->linesize[2] = videoCodecContext->width / 2;
frame->format = (int)AVPixelFormat.AV_PIX_FMT_YUV420P;
frame->width = videoCodecContext->width;
frame->height = videoCodecContext->height;
// 推流
while (true)
{
// 从输入文件读取一帧视频
AVPacket* inputPacket = ffmpeg.av_packet_alloc();
if (ffmpeg.av_read_frame(inputContext, inputPacket) != 0)
{
break;
}
if (inputPacket->stream_index != videoStreamIndex)
{
ffmpeg.av_packet_unref(inputPacket);
continue;
}
if (ffmpeg.avcodec_send_packet(videoCodecContext, inputPacket) != 0)
{
ffmpeg.av_packet_unref(inputPacket);
continue;
}
while (ffmpeg.avcodec_receive_frame(videoCodecContext, frame) == 0)
{
// 编码视频帧
AVPacket* outputPacket = ffmpeg.av_packet_alloc();
if (ffmpeg.avcodec_send_frame(outputVideoCodecContext, frame) != 0)
{
ffmpeg.av_packet_unref(outputPacket);
continue;
}
if (ffmpeg.avcodec_receive_packet(outputVideoCodecContext, outputPacket) != 0)
{
ffmpeg.av_packet_unref(outputPacket);
continue;
}
// 写入输出文件
outputPacket->stream_index = videoStreamIndex;
outputPacket->pts = ffmpeg.av_rescale_q(outputPacket->pts, outputVideoCodecContext->time_base, videoStream->time_base);
outputPacket->dts = ffmpeg.av_rescale_q(outputPacket->dts, outputVideoCodecContext->time_base, videoStream->time_base);
outputPacket->duration = ffmpeg.av_rescale_q(outputPacket->duration, outputVideoCodecContext->time_base, videoStream->time_base);
if (ffmpeg.av_interleaved_write_frame(outputContext, outputPacket) != 0)
{
Console.WriteLine('Could not write output packet.');
}
ffmpeg.av_packet_unref(outputPacket);
}
ffmpeg.av_packet_unref(inputPacket);
}
// 写入输出文件尾
if (ffmpeg.av_write_trailer(outputContext) != 0)
{
Console.WriteLine('Could not write output file trailer.');
return;
}
// 释放资源
ffmpeg.avformat_close_input(&inputContext);
ffmpeg.avcodec_close(videoCodecContext);
ffmpeg.avcodec_free_context(&outputVideoCodecContext);
if ((outputContext->oformat->flags & ffmpeg.AVFMT_NOFILE) == 0)
{
ffmpeg.avio_close(outputContext->pb);
}
ffmpeg.avformat_free_context(outputContext);
ffmpeg.av_packet_free(&packet);
ffmpeg.av_frame_free(&frame);
handle.Free();
}
}
}
在这个示例中,我们首先使用 FFmpeg 打开视频输入文件(这里使用的是 video4linux2 设备文件),查找视频流,打开视频解码器。
然后,我们初始化输出上下文,添加视频流到输出上下文,打开视频编码器,并初始化输出文件。
在推流循环中,我们从输入文件读取一帧视频,解码视频帧,编码视频帧,并将编码后的视频帧写入输出文件。
最后,我们写入输出文件尾,释放资源。
原文地址: https://www.cveoy.top/t/topic/ndNV 著作权归作者所有。请勿转载和采集!