明天用的代码

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)
            {
                AVInputFormat* iformat = ffmpeg.av_find_input_format("h264");
                error = ffmpeg.avformat_open_input(&pFormatContext, url, iformat, 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 = pStream->time_base;
            outVStream->coder_info_nb_frames = pStream->coder_info_nb_frames;
            outVStream->nb_decoded_frames = pStream->nb_decoded_frames;
            outVStream->pts_wrap_bits = pStream->pts_wrap_bits;
            outVStream->duration = pStream->duration;
            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;
            int totalpkt=0;
            int temdts=0;
            //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;
                    if(totalpkt==0){
                         pkt-> dts =-(pkt-> duration)
                         temdts= pkt->dts
                     } else{
                        pkt-> dts = temdts+pkt-> duration
                        temdts= pkt->dts
                     }
                    pkt-> pts= pkt->dts
                    totalpkt++
                    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;
        }
    }
}
posted @ 2023-03-14 17:15  zqy123  阅读(26)  评论(0编辑  收藏  举报