• FFMpeg.AutoGen(2)讲解官方example代码:编码


    官方Example代码里编码的作业:把上一讲解码出来的jpg图片,编码成264格式的文件。

    编码的流程,也是本文目录

    1.把图片转换byte[]

    2.把图片转换成yuv格式,封装成avframe。

    3.编码。

    先贴出编码的主函数。函数里分析下来,就是通过调用相关类和函数实现的目录三步。

      1         /// <summary>
      2         /// 编码 把解码出来的jpg文件,再编码成UV420P
      3         /// </summary>
      4         private static unsafe void EncodeImagesToH264()
      5         {
      6 
      7             //获取解码出来的文件队列
      8             var frameFiles = Directory.GetFiles(".", "frame.*.jpg").OrderBy(x => x).ToArray();
      9             //获取第一张帧图片
     10             var fistFrameImage = Image.FromFile(frameFiles.First());
     11 
     12             //设置导出媒体信息
     13             var outputFileName = "out.h264";
     14             var fps = 25;
     15             var sourceSize = fistFrameImage.Size;
     16             var sourcePixelFormat = AVPixelFormat.AV_PIX_FMT_BGR24;
     17             var destinationSize = sourceSize;
     18             var destinationPixelFormat = AVPixelFormat.AV_PIX_FMT_YUV420P;
     19             //创建格式转换其 把rgb 转变成yuv ,同时对分辨率进行缩放
     20             using (var vfc = new VideoFrameConverter(sourceSize, sourcePixelFormat, destinationSize, destinationPixelFormat))
     21             {
     22                 // be advise only ffmpeg based player (like ffplay or vlc) can play this file, for the others you need to go through muxing
     23                 //建议基于ffmpeg的播放器播放这个文件out.h264,否则需要多路复用技术
     24                 //这个文件就是用ffmpeg把rgb图片转变成264的一个个帧。
     25                 using (var fs = File.Open(outputFileName, FileMode.Create))
     26 
     27                 {
     28                     //创建264转换 把要保存的文件句柄fs 帧率fps 源大小destinationSize 传入
     29                     using (var vse = new H264VideoStreamEncoder(fs, fps, destinationSize))
     30                     {
     31                         var frameNumber = 0;
     32                         //读取每一张图片,作为一帧
     33                         foreach (var frameFile in frameFiles)
     34                         {
     35                             byte[] bitmapData;
     36 
     37                             using (var frameImage = Image.FromFile(frameFile))
     38                             using (var frameBitmap = frameImage is Bitmap bitmap ? bitmap : new Bitmap(frameImage))// is 后面接变量申明 这个写法比较有意思
     39                             {
     40                                 bitmapData = GetBitmapData(frameBitmap);
     41                             }
     42                             //固化pBitmapData内存地址
     43                             fixed (byte* pBitmapData = bitmapData)
     44                             {
     45                                 //指针数组用于保存指向帧实际内存空间的地址
     46                                 var data = new byte_ptrArray8 { [0] = pBitmapData };
     47                                 //每行大小
     48                                 var linesize = new int_array8 { [0] = bitmapData.Length / sourceSize.Height };
     49                                 var frame = new AVFrame
     50                                 {
     51                                     data = data,
     52                                     linesize = linesize,
     53                                     height = sourceSize.Height
     54                                 };
     55                                 //把rgb转换为yuv,同时对分辨率进行缩放
     56                                 var convertedFrame = vfc.Convert(frame);
     57                                 //设置时间戳 帧的序号 x 帧率
     58                                 convertedFrame.pts = frameNumber * fps;
     59                                 //把yuv420p编码成264,并写到 "out.h264"文件中
     60                                 vse.Encode(convertedFrame);
     61                             }
     62 
     63                             Console.WriteLine($"frame: {frameNumber}");
     64                             frameNumber++;
     65                         }
     66                     }
     67                 }
     68             }
     69         }
     70 

    1.把图片转换byte[]

    主要使用这个函数:bitmapData = GetBitmapData(frameBitmap); 函数简单代码很少,直接看代码和我的注释即可。

      1         /// <summary>
      2         /// 把bitmap转换为byte[]
      3         /// 从Scan0开始把每个像素字节返回成数组byte[]
      4         /// </summary>
      5         /// <param name="frameBitmap"></param>
      6         /// <returns></returns>
      7         private static byte[] GetBitmapData(Bitmap frameBitmap)
      8         {
      9             var bitmapData = frameBitmap.LockBits(new Rectangle(Point.Empty, frameBitmap.Size), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
     10             try
     11             {
     12                 //Stride像素实际占据字节长度
     13                 var length = bitmapData.Stride * bitmapData.Height;
     14                 var data = new byte[length];
     15                 //Scan0 放位图像素内存中的第一个地址
     16                 Marshal.Copy(bitmapData.Scan0, data, 0, length);
     17                 return data;
     18             }
     19             finally
     20             {
     21                 frameBitmap.UnlockBits(bitmapData);
     22             }
     23         }
     24     }

    2.把图片转换成yuv格式,封装成avframe。

    使用VideoFrameConverter类,进行图像格式转换从rgb转为yuv,此类在上一篇有具体描述这里不再阐述:

    var sourcePixelFormat = AVPixelFormat.AV_PIX_FMT_BGR24;

    var destinationPixelFormat = AVPixelFormat.AV_PIX_FMT_YUV420P;

    var vfc = new VideoFrameConverter(sourceSize, sourcePixelFormat, destinationSize, destinationPixelFormat)

    从上面代码看到,在申明和实例化时,告诉VideoFrameConverter对象源格式24bit rgb 需要转换为 yuv420p。同时指定了源和目的的尺寸,可以缩放。

    并通过vfc.Convert(frame)转换成yuv格式。返回的数据是封装好的AVFrame格式。

    3.编码

    通过实现H264VideoStreamEncoder类实现编码264。编码的核心使用的是依旧是解码器AVCodecContext此时它应该被成为编码器。因为是编码,所以没有解码获取媒体信息获取有效流索引这些操作。建议阅读源码。文件末尾附笔者注释过的源码。

    流程为

    3.1.创建编码器

    通过ffmpeg.avcodec_alloc_context3(_pCodec)创建AVCodecContext。这里的_pCodec是通过ffmpeg.avcodec_find_encoder(AVCodecID.AV_CODEC_ID_H264)获取的。

    3.2.配置编码器

    配置AVCodecContext的widthheight ime_base(时间戳基准,这里是1/fps ,时间基准的详细内容可看参考文档【2】)pix_fmt(帧像素格式,也就是源格式)设置参数preset 值为veryslow(264的参数,使用函数ffmpeg.av_opt_set(_pCodecContext->priv_data, "preset", "veryslow", 0)设置)。

    3.3.打开编码器

    ffmpeg.avcodec_open2(_pCodecContext, _pCodec, null)。


    4.轮询编码

    实例外,循环调用Encode(AVFrame)进行编码。合计3步,编码2步,写入文件流1步:

    1)放入编码器ffmpeg.avcodec_send_frame(_pCodecContext, &frame)

    2)从编码器读取编码后的帧 通过参数返回 error = ffmpeg.avcodec_receive_packet(_pCodecContext, pPacket)。

    3)把编码后的AVPacket包格式用UnmanagedMemoryStream写入文件流。


    参考文档:

    【1】FFmpeg X264的preset和tune 2017-05-25 Lerry.Zhao

    【2】ffmpeg里time_base总结 2016-11-02 耕地

    附件:

      1 using System;
      2 using System.Drawing;
      3 using System.IO;
      4 
      5 namespace FFmpeg.AutoGen.Example
      6 {
      7      /// <summary>
      8      /// H264转换类
      9      /// </summary>
     10     public sealed unsafe class H264VideoStreamEncoder : IDisposable
     11     {
     12         private readonly Size _frameSize;
     13         private readonly int _linesizeU;
     14         private readonly int _linesizeV;
     15         private readonly int _linesizeY;
     16         private readonly AVCodec* _pCodec;
     17         private readonly AVCodecContext* _pCodecContext;
     18         private readonly Stream _stream;
     19         private readonly int _uSize;
     20         private readonly int _ySize;
     21 
     22         /// <summary>
     23         /// 构造H264VideoStreamEncoder
     24         /// </summary>
     25         /// <param name="stream">转换源流</param>
     26         /// <param name="fps">帧率信息</param>
     27         /// <param name="frameSize">帧大小</param>
     28         public H264VideoStreamEncoder(Stream stream, int fps, Size frameSize)
     29         {
     30             _stream = stream;
     31             _frameSize = frameSize;
     32 
     33             var codecId = AVCodecID.AV_CODEC_ID_H264;
     34             _pCodec = ffmpeg.avcodec_find_encoder(codecId);
     35             if (_pCodec == null) throw new InvalidOperationException("Codec not found.");
     36             //根据解码器分配一个AVCodecContext ,仅仅分配工具,还没有初始化。
     37             _pCodecContext = ffmpeg.avcodec_alloc_context3(_pCodec);
     38             //配置解码器格式信息
     39             _pCodecContext->width = frameSize.Width;
     40             _pCodecContext->height = frameSize.Height;
     41             _pCodecContext->time_base = new AVRational {num = 1, den = fps};
     42             _pCodecContext->pix_fmt = AVPixelFormat.AV_PIX_FMT_YUV420P;
     43             //设置参数preset 值为veryslow 配置264的参数 
     44             ffmpeg.av_opt_set(_pCodecContext->priv_data, "preset", "veryslow", 0);
     45             //打开编码器
     46             ffmpeg.avcodec_open2(_pCodecContext, _pCodec, null).ThrowExceptionIfError();
     47             //每一行yuv的大小 每个像素的y值是记录的。相邻两行的各两个像素共享一个UV
     48             _linesizeY = frameSize.Width;
     49             _linesizeU = frameSize.Width / 2;
     50             _linesizeV = frameSize.Width / 2;
     51             //y的大小就是像素的数量 uv只有像素的1/4 四个像素共享一个uv
     52             _ySize = _linesizeY * frameSize.Height;
     53             _uSize = _linesizeU * frameSize.Height / 2;
     54         }
     55 
     56         public void Dispose()
     57         {
     58             ffmpeg.avcodec_close(_pCodecContext);
     59             ffmpeg.av_free(_pCodecContext);
     60             ffmpeg.av_free(_pCodec);
     61         }
     62 
     63         /// <summary>
     64         /// 编码成264格式
     65         /// </summary>
     66         /// <param name="frame">源帧</param>
     67         public void Encode(AVFrame frame)
     68         {
     69             if (frame.format != (int) _pCodecContext->pix_fmt) throw new ArgumentException("Invalid pixel format.", nameof(frame));
     70             if (frame.width != _frameSize.Width) throw new ArgumentException("Invalid width.", nameof(frame));
     71             if (frame.height != _frameSize.Height) throw new ArgumentException("Invalid height.", nameof(frame));
     72             if (frame.linesize[0] != _linesizeY) throw new ArgumentException("Invalid Y linesize.", nameof(frame));
     73             if (frame.linesize[1] != _linesizeU) throw new ArgumentException("Invalid U linesize.", nameof(frame));
     74             if (frame.linesize[2] != _linesizeV) throw new ArgumentException("Invalid V linesize.", nameof(frame));
     75             if (frame.data[1] - frame.data[0] != _ySize) throw new ArgumentException("Invalid Y data size.", nameof(frame));
     76             if (frame.data[2] - frame.data[1] != _uSize) throw new ArgumentException("Invalid U data size.", nameof(frame));
     77 
     78             //创建AVPacket包
     79             var pPacket = ffmpeg.av_packet_alloc();
     80             try
     81             {
     82                 int error;
     83                 do
     84                 {
     85                     //把帧放入解码器
     86                     ffmpeg.avcodec_send_frame(_pCodecContext, &frame).ThrowExceptionIfError();
     87                     //从解码器里读取帧,放到pPacket包里
     88                     error = ffmpeg.avcodec_receive_packet(_pCodecContext, pPacket);
     89                 } while (error == ffmpeg.AVERROR(ffmpeg.EAGAIN));
     90 
     91                 error.ThrowExceptionIfError();
     92                 //UnmanagedMemoryStream 类提供从托管代码访问非托管内存块的能
     93                 //把包里的数据写入_stream(构造函数传入)
     94                 using (var packetStream = new UnmanagedMemoryStream(pPacket->data, pPacket->size)) packetStream.CopyTo(_stream);
     95             }
     96             finally
     97             {
     98                 ffmpeg.av_packet_unref(pPacket);
     99             }
    100         }
    101     }
    102 }
    View Code
  • 相关阅读:
    jquery点击事件后增加克隆的标签,并改变克隆的属性加入
    jQuery 文本插入和标签移动方法
    用jquery来实现正反选选择框checkbox的小示例
    js 常用事件句柄总结
    jQuery 菜单小练习(实现点击和移动鼠标效果)
    jQuery 选择器
    js 中移动元素的方法
    《JavaScript总结》深拷贝和浅拷贝
    《JavaScript总结》js的运行机制
    git 撤回放到暂存区的文件
  • 原文地址:https://www.cnblogs.com/edzjx/p/12913257.html
Copyright © 2020-2023  润新知