using FFmpeg.AutoGen;
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
namespace FFmpegDemo
{
public unsafe class tstRtmp
{
/// <summary>
/// 显示图片委托
/// </summary>
/// <param name="bitmap"></param>
public delegate void ShowBitmap(Bitmap bitmap);
/// <summary>
/// 执行控制变量
/// </summary>
bool CanRun;
/// <summary>
/// 对读取的264数据包进行解码和转换
/// </summary>
/// <param name="show">解码完成回调函数</param>
/// <param name="url">播放地址,也可以是本地文件地址</param>
public unsafe void Start(ShowBitmap show, string url)
{
CanRun = true;
Console.WriteLine(@"Current directory: " + Environment.CurrentDirectory);
Console.WriteLine(@"Runnung in {0}-bit mode.", Environment.Is64BitProcess ? @"64" : @"32");
//FFmpegDLL目录查找和设置
FFmpegBinariesHelper.RegisterFFmpegBinaries();
#region ffmpeg 初始化
// 初始化注册ffmpeg相关的编码器
ffmpeg.av_register_all();
ffmpeg.avcodec_register_all();
ffmpeg.avformat_network_init();
Console.WriteLine($"FFmpeg version info: {ffmpeg.av_version_info()}");
#endregion
#region ffmpeg 日志
// 设置记录ffmpeg日志级别
ffmpeg.av_log_set_level(ffmpeg.AV_LOG_VERBOSE);
av_log_set_callback_callback logCallback = (p0, level, format, vl) =>
{
if (level > ffmpeg.av_log_get_level()) return;
var lineSize = 1024;
var lineBuffer = stackalloc byte[lineSize];
var printPrefix = 1;
ffmpeg.av_log_format_line(p0, level, format, vl, lineBuffer, lineSize, &printPrefix);
var line = Marshal.PtrToStringAnsi((IntPtr)lineBuffer);
Console.Write(line);
};
ffmpeg.av_log_set_callback(logCallback);
#endregion
#region ffmpeg 转码
// 分配音视频格式上下文
var pFormatContext = ffmpeg.avformat_alloc_context();
int error;
//打开流
error = ffmpeg.avformat_open_input(&pFormatContext, url, null, null);
if (error != 0) throw new ApplicationException(GetErrorMessage(error));
// 读取媒体流信息
error = ffmpeg.avformat_find_stream_info(pFormatContext, null);
if (error != 0) throw new ApplicationException(GetErrorMessage(error));
// 这里只是为了打印些视频参数
AVDictionaryEntry* tag = null;
while ((tag = ffmpeg.av_dict_get(pFormatContext->metadata, "", tag, ffmpeg.AV_DICT_IGNORE_SUFFIX)) != null)
{
var key = Marshal.PtrToStringAnsi((IntPtr)tag->key);
var value = Marshal.PtrToStringAnsi((IntPtr)tag->value);
Console.WriteLine($"{key} = {value}");
}
// 从格式化上下文获取流索引
AVStream* pStream = null, aStream;
for (var i = 0; i < pFormatContext->nb_streams; i++)//nb_streams 视音频流的个数
{
if (pFormatContext->streams[i]->codec->codec_type == AVMediaType.AVMEDIA_TYPE_VIDEO)
{
pStream = pFormatContext->streams[i];
}
else if (pFormatContext->streams[i]->codec->codec_type == AVMediaType.AVMEDIA_TYPE_AUDIO)
{
aStream = pFormatContext->streams[i];
}
}
if (pStream == null) throw new ApplicationException(@"Could not found video stream.");
// 获取流的编码器上下文
var codecContext = *pStream->codec;
// 3-14 zqy 通过codecPara将获取视频流的编码参数
AVCodecParameters* codecPara = pStream->codecpar;
Console.WriteLine("===========================");
ffmpeg.av_dump_format(pFormatContext, 0, url, 0);
Console.WriteLine("===========================");
Console.WriteLine($"codec name: {ffmpeg.avcodec_get_name(codecContext.codec_id)}");
// 获取图像的宽、高及像素格式
var width = codecContext.width;
var height = codecContext.height;
var sourcePixFmt = codecContext.pix_fmt;
// 得到编码器ID
var codecId = codecContext.codec_id;
// 目标像素格式
var destinationPixFmt = AVPixelFormat.AV_PIX_FMT_BGR24;
// 某些264格式codecContext.pix_fmt获取到的格式是AV_PIX_FMT_NONE 统一都认为是YUV420P
if (sourcePixFmt == AVPixelFormat.AV_PIX_FMT_NONE && codecId == AVCodecID.AV_CODEC_ID_H264)
{
sourcePixFmt = AVPixelFormat.AV_PIX_FMT_YUV420P;
}
// 得到SwsContext对象:用于图像的缩放和转换操作
var pConvertContext = ffmpeg.sws_getContext(width, height, sourcePixFmt,
width, height, destinationPixFmt,
ffmpeg.SWS_FAST_BILINEAR, null, null, null);
if (pConvertContext == null) throw new ApplicationException(@"Could not initialize the conversion context.");
//分配一个默认的帧对象:AVFrame
var pConvertedFrame = ffmpeg.av_frame_alloc();
// 目标媒体格式需要的字节长度
var convertedFrameBufferSize = ffmpeg.av_image_get_buffer_size(destinationPixFmt, width, height, 1);
// 分配目标媒体格式内存使用
var convertedFrameBufferPtr = Marshal.AllocHGlobal(convertedFrameBufferSize);
var dstData = new byte_ptrArray4();
var dstLinesize = new int_array4();
// 设置图像填充参数
ffmpeg.av_image_fill_arrays(ref dstData, ref dstLinesize, (byte*)convertedFrameBufferPtr, destinationPixFmt, width, height, 1);
#endregion
#region ffmpeg 解码
// 根据编码器ID获取对应的解码器
var pCodec = ffmpeg.avcodec_find_decoder(codecId);
if (pCodec == null) throw new ApplicationException(@"Unsupported codec.");
var pCodecContext = &codecContext;
if ((pCodec->capabilities & ffmpeg.AV_CODEC_CAP_TRUNCATED) == ffmpeg.AV_CODEC_CAP_TRUNCATED)
pCodecContext->flags |= ffmpeg.AV_CODEC_FLAG_TRUNCATED;
// 通过解码器打开解码器上下文:AVCodecContext pCodecContext
error = ffmpeg.avcodec_open2(pCodecContext, pCodec, null);
if (error < 0) throw new ApplicationException(GetErrorMessage(error));
// 分配解码帧对象:AVFrame pDecodedFrame
var pDecodedFrame = ffmpeg.av_frame_alloc();
// 初始化媒体数据包
var packet = new AVPacket();
var pPacket = &packet;
ffmpeg.av_init_packet(pPacket);
var frameNumber = 0;
//3-14 zqy=================change============================================
AVFormatContext* outFmtCtx = null;
int outVStreamIndex = -1;
String outFileName = "C:\\Users\\ZQY\\Desktop\\研究生资料全\\项目相关\\沈飞项目\\unloadingTest.mp4";
//=====================输出部分=========================//
//打开输出文件并填充格式数据
//参数一:函数调用成功之后创建的AVFormatContext结构体。
//参数二:指定AVFormatContext中的AVOutputFormat,确定输出格式。指定为NULL,设定后两个参数(format_name或者filename)由FFmpeg猜测输出格式。。
//参数三:使用该参数需要自己手动获取AVOutputFormat,相对于使用后两个参数来说要麻烦一些。
//参数四:指定输出格式的名称。根据格式名称,FFmpeg会推测输出格式。输出格式可以是“flv”,“mkv”等等。
error = ffmpeg.avformat_alloc_output_context2(&outFmtCtx, null, null, outFileName);
//打开输出文件并填充数据
error = ffmpeg.avio_open(&outFmtCtx->pb,outFileName,ffmpeg.AVIO_FLAG_READ_WRITE);
AVStream* outVStream = ffmpeg.avformat_new_stream(outFmtCtx, null);//记录视频流通道数目。存储视频流通道。
if (outVStream == null)
{
Console.WriteLine("Failed allocating output stream.\n");
return;
}
//outVStream->time_base.den = 25;//AVRational这个结构标识一个分数,num为分数,den为分母(时间的刻度)
//outVStream->time_base.num = 1;
outVStream->time_base.den = pCodecContext->time_base.den;
outVStream->time_base.num = pCodecContext->time_base.num;
outVStreamIndex = outVStream->index;
// 查找编码器
//参数一:id请求的编码器的AVCodecID
//参数二:如果找到一个编码器,则为NULL。
//H264/H265码流后,再调用avcodec_find_decoder解码后,再写入到/MP4文件中去
AVCodec* outCodec = ffmpeg.avcodec_find_decoder(codecPara->codec_id);
if (outCodec == null)
{
Console.WriteLine("Cannot find any encoder.\n");
return;
}
//从输入的h264编码器数据复制一份到输出文件的编码器中
AVCodecContext* outCodecCtx = ffmpeg.avcodec_alloc_context3(outCodec); //申请AVCodecContext空间。需要传递一个编码器,也可以不传,但不会包含编码器。
//AVCodecParameters与AVCodecContext里的参数有很多相同的
AVCodecParameters* outCodecPara = outFmtCtx->streams[outVStream->index]->codecpar;
//avcodec_parameters_copy()来copyAVCodec的上下文。
if (ffmpeg.avcodec_parameters_copy(outCodecPara, codecPara) < 0)
{
Console.WriteLine("Cannot copy codec para.\n");
return;
}
//设置编码器参数(不同参数对视频编质量或大小的影响)
/*outCodecCtx->time_base.den=25;
outCodecCtx->time_base.num=1;*/
/*
outCodecCtx->bit_rate = 0;//目标的码率,即采样的码率;显然,采样码率越大,视频大小越大 比特率
outCodecCtx->time_base.num = 1;//下面两行:一秒钟25帧
outCodecCtx->time_base.den = 15;
outCodecCtx->frame_number = 1;//每包一个视频帧
*/
outCodecCtx->bit_rate = pCodecContext->bit_rate;
outCodecCtx->time_base.num = pCodecContext->time_base.num;
outCodecCtx->time_base.den = pCodecContext->time_base.den;
outCodecCtx->frame_number = pCodecContext->frame_number;
//打开输出文件需要的编码器
if (ffmpeg.avcodec_open2(outCodecCtx, outCodec, null) < 0)
{
Console.WriteLine("Cannot open output codec.\n");
return;
}
Console.WriteLine("============Output Information=============>\n");
ffmpeg.av_dump_format(outFmtCtx, 0, outFileName, 1);//输出视频信息
Console.WriteLine("============Output Information=============<\n");
// 写入文件头
if (ffmpeg.avformat_write_header(outFmtCtx, null) < 0)
{
Console.WriteLine("Cannot write header to file.\n");
return;
}
int frame_index = 0;//统计帧数
//===============编码部分===============//
//AVPacket 数据结构 显示时间戳(pts)、解码时间戳(dts)、数据时长,所在媒体流的索引等
AVPacket* pkt = ffmpeg.av_packet_alloc();
//存储每一个视频/音频流信息的结构体 这里的pStream只有视频流 aStream才包括音频流
AVStream* inVStream = pStream;
//ffmpeg.avio_read(pFormatContext,pkt,pkt->size);
//ffmpeg.avio_write();
while (ffmpeg.av_read_frame(pFormatContext, pkt) >= 0)
{//循环读取每一帧直到读完
// pkt->dts = 0;//不加这个时间戳会出问题,时间戳比之前小的话 FFmpeg会选择丢弃视频包,现在给视频包打时间戳可以重0开始依次递增。
if (pkt->stream_index == pStream->index)
{//确保处理的是视频流 stream_index标识该AVPacket所属的视频/音频流。
//FIXME:No PTS (Example: Raw H.264)
//Simple Write PTS
//如果当前处理帧的显示时间戳为0或者没有等等不是正常值
if (pkt->pts == ffmpeg.AV_NOPTS_VALUE)
{
Console.WriteLine("frame_index:%d\n", frame_index);
//Write PTS时间 刻度
AVRational time_base1 = inVStream->time_base;
//Duration between 2 frames (us) 时长
//AV_TIME_BASE 时间基
//av_q2d(AVRational);该函数负责把AVRational结构转换成double,通过这个函数可以计算出某一帧在视频中的时间位置
//r_frame_rate
//Int64 calc_duration = (double)ffmpeg.AV_TIME_BASE / ffmpeg.av_q2d(inVStream->r_frame_rate);
//Parameters参数
// pkt->pts = (double)(frame_index * calc_duration) / (double)(ffmpeg.(time_base1) * ffmpeg.AV_TIME_BASE);
//pkt->dts = pkt->pts;
//pkt->duration = pFormatContext->duration;
//frame_index++;
}
//Convert PTS/DTS
//AVPacket
// pts 显示时间戳
// dts 解码时间戳
// duration 数据的时长,以所属媒体流的时间基准为单位
// pos 数据在媒体流中的位置,未知则值为-1
// 标识该AVPacket所属的视频/音频流。
pkt->pts = ffmpeg.av_rescale_q_rnd(pkt->pts, inVStream->time_base, outVStream->time_base, AVRounding.AV_ROUND_INF | AVRounding.AV_ROUND_PASS_MINMAX);
pkt->dts = ffmpeg.av_rescale_q_rnd(pkt->dts, inVStream->time_base, outVStream->time_base, AVRounding.AV_ROUND_NEAR_INF | AVRounding.AV_ROUND_PASS_MINMAX);
pkt->duration = ffmpeg.av_rescale_q(pkt->duration, inVStream->time_base, outVStream->time_base);
//pkt->pos = -1;
pkt->stream_index = outVStreamIndex;
Console.WriteLine("Write 1 Packet. size:%5d\tpts:%ld\n", pkt->size, pkt->pts);
//Write
if (ffmpeg.av_interleaved_write_frame(outFmtCtx, pkt) < 0) {
Console.WriteLine("Error muxing packet\n");
break;
}
//处理完压缩数据之后,并且在进入下一次循环之前,
//记得使用 av_packet_unref 来释放已经分配的AVPacket->data缓冲区。
ffmpeg.av_packet_unref(pkt);
}
}
ffmpeg.av_write_trailer(outFmtCtx);
//=================释放所有指针=======================
ffmpeg.av_packet_free(&pkt);//堆栈上数据缓存空间
ffmpeg.av_free(inVStream);//存储每一个视频/音频流信息的结构体
ffmpeg.av_free(outVStream);//在输出的mp4文件中创建一条视频流
ffmpeg.avformat_close_input(&outFmtCtx);//关闭一个AVFormatContext
ffmpeg.avcodec_close(outCodecCtx);
ffmpeg.avcodec_free_context(&outCodecCtx);
ffmpeg.av_free(outCodec);
ffmpeg.avcodec_parameters_free(&outCodecPara);
ffmpeg.avcodec_parameters_free(&codecPara);
ffmpeg.avio_close(outFmtCtx->pb);
//=================change============================================
while (CanRun)
{
try
{
do
{
// 读取一帧未解码数据
error = ffmpeg.av_read_frame(pFormatContext, pPacket);
pkt->dts = 0;
//Convert PTS/DTS
//AVPacket
// pts 显示时间戳
// dts 解码时间戳
// duration 数据的时长,以所属媒体流的时间基准为单位
// pos 数据在媒体流中的位置,未知则值为-1
// 标识该AVPacket所属的视频/音频流。
pkt->pts = ffmpeg.av_rescale_q_rnd(pkt->pts, inVStream->time_base, outVStream->time_base, AVRounding.AV_ROUND_INF | AVRounding.AV_ROUND_PASS_MINMAX);
pkt->dts = ffmpeg.av_rescale_q_rnd(pkt->dts, inVStream->time_base, outVStream->time_base, AVRounding.AV_ROUND_NEAR_INF | AVRounding.AV_ROUND_PASS_MINMAX);
pkt->duration = ffmpeg.av_rescale_q(pkt->duration, inVStream->time_base, outVStream->time_base);
pkt->pos = -1;
pkt->stream_index = outVStreamIndex;
if (ffmpeg.av_interleaved_write_frame(outFmtCtx, pkt) < 0)
{
Console.WriteLine("Error muxing packet\n");
//break;
}
// Console.WriteLine(pPacket->dts);
if (error == ffmpeg.AVERROR_EOF) break;
if (error < 0) throw new ApplicationException(GetErrorMessage(error));
if (pPacket->stream_index != pStream->index) continue;
// 解码
error = ffmpeg.avcodec_send_packet(pCodecContext, pPacket);
if (error < 0) throw new ApplicationException(GetErrorMessage(error));
// 解码输出解码数据
error = ffmpeg.avcodec_receive_frame(pCodecContext, pDecodedFrame);
} while (error == ffmpeg.AVERROR(ffmpeg.EAGAIN) && CanRun);
if (error == ffmpeg.AVERROR_EOF) break;
if (error < 0) throw new ApplicationException(GetErrorMessage(error));
if (pPacket->stream_index != pStream->index) continue;
//Console.WriteLine($@"frame: {frameNumber}");
// YUV->RGB
ffmpeg.sws_scale(pConvertContext, pDecodedFrame->data, pDecodedFrame->linesize, 0, height, dstData, dstLinesize);
}
finally
{
ffmpeg.av_packet_unref(pPacket);//释放数据包对象引用
ffmpeg.av_frame_unref(pDecodedFrame);//释放解码帧对象引用
}
ffmpeg.av_packet_unref(pkt);
// 封装Bitmap图片
var bitmap = new Bitmap(width, height, dstLinesize[0], PixelFormat.Format24bppRgb, convertedFrameBufferPtr);
// 回调
show(bitmap);
//bitmap.Save(AppDomain.CurrentDomain.BaseDirectory + "\\264\\frame.buffer."+ frameNumber + ".jpg", ImageFormat.Jpeg);
frameNumber++;
}
ffmpeg.av_write_trailer(outFmtCtx);
/*
while (CanRun)
{
try
{
do
{
// 读取一帧未解码数据
error = ffmpeg.av_read_frame(pFormatContext, pPacket);
error = ffmpeg.av_interleaved_write_frame(outFmtCtx, pkt);
// Console.WriteLine(pPacket->dts);
if (error == ffmpeg.AVERROR_EOF) break;
if (error < 0) throw new ApplicationException(GetErrorMessage(error));
if (pPacket->stream_index != pStream->index) continue;
// 解码
error = ffmpeg.avcodec_send_packet(pCodecContext, pPacket);
if (error < 0) throw new ApplicationException(GetErrorMessage(error));
// 解码输出解码数据
error = ffmpeg.avcodec_receive_frame(pCodecContext, pDecodedFrame);
} while (error == ffmpeg.AVERROR(ffmpeg.EAGAIN) && CanRun);
if (error == ffmpeg.AVERROR_EOF) break;
if (error < 0) throw new ApplicationException(GetErrorMessage(error));
if (pPacket->stream_index != pStream->index) continue;
//Console.WriteLine($@"frame: {frameNumber}");
// YUV->RGB
ffmpeg.sws_scale(pConvertContext, pDecodedFrame->data, pDecodedFrame->linesize, 0, height, dstData, dstLinesize);
}
finally
{
ffmpeg.av_packet_unref(pPacket);//释放数据包对象引用
ffmpeg.av_frame_unref(pDecodedFrame);//释放解码帧对象引用
}
ffmpeg.av_packet_unref(pkt);
// 封装Bitmap图片
var bitmap = new Bitmap(width, height, dstLinesize[0], PixelFormat.Format24bppRgb, convertedFrameBufferPtr);
// 回调
show(bitmap);
//bitmap.Save(AppDomain.CurrentDomain.BaseDirectory + "\\264\\frame.buffer."+ frameNumber + ".jpg", ImageFormat.Jpeg);
frameNumber++;
}
*/
//播放完置空播放图片
show(null);
#endregion
#region 释放资源
Marshal.FreeHGlobal(convertedFrameBufferPtr);
ffmpeg.av_free(pConvertedFrame);
ffmpeg.sws_freeContext(pConvertContext);
ffmpeg.av_free(pDecodedFrame);
ffmpeg.avcodec_close(pCodecContext);
ffmpeg.avformat_close_input(&pFormatContext);
#endregion
}
/// <summary>
/// 获取ffmpeg错误信息
/// </summary>
/// <param name="error"></param>
/// <returns></returns>
private static unsafe string GetErrorMessage(int error)
{
var bufferSize = 1024;
var buffer = stackalloc byte[bufferSize];
ffmpeg.av_strerror(error, buffer, (ulong)bufferSize);
var message = Marshal.PtrToStringAnsi((IntPtr)buffer);
return message;
}
public void Stop()
{
CanRun = false;
}
}
}
标签:明天,ffmpeg,pkt,代码,av,time,error,var
From: https://www.cnblogs.com/zhouqingyi/p/17215634.html