C#基于FFmpeg.autogen播放rtsp流实现暂停和继续播放

ffmpeg.autogen库中有两个函数ffmpeg.av_read_pause和ffmpeg.av_read_play,再配合是否处于暂停中的标识进行控制循环,从而达到暂停和继续播放的目的。下面是示例代码:

/// <summary>
/// 对读取的264数据包进行解码和转换
/// </summary>
/// <param name="show">解码完成回调函数</param>
/// <param name="url">播放地址,也可以是本地文件地址</param>
public unsafe void Start(ShowBitmap show, string url, out string strResult)
{
    strResult = "";
    try
    {
        CanRun = true;

        #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 转码


        // 分配音视频格式上下文
        pFormatContext = ffmpeg.avformat_alloc_context();
        var pFormatContextTemp = pFormatContext;
        // 增加缓冲区大小
        //pFormatContextTemp->probesize = 100 * 1024 * 1024; // 100 MB
        //pFormatContextTemp->max_analyze_duration = 5 * ffmpeg.AV_TIME_BASE; // 5 seconds

        int error;

        //打开流
        Stopwatch stopwatch = new Stopwatch();
        stopwatch.Start();
        AVDictionary* options = null;
        ffmpeg.av_dict_set(&options, "rtsp_transport", "tcp", 0);//指定拉流网络类型
        error = ffmpeg.avformat_open_input(&pFormatContextTemp, url, null, &options);
        if (error != 0) throw new ApplicationException(GetErrorMessage(error));
        stopwatch.Stop();
        Console.Write($"打开流 使用时间:{stopwatch.ElapsedMilliseconds}");

        // 读取媒体流信息
        stopwatch = new Stopwatch();
        stopwatch.Start();
        error = ffmpeg.avformat_find_stream_info(pFormatContext, null);
        if (error != 0) throw new ApplicationException(GetErrorMessage(error));
        stopwatch.Stop();
        Console.Write($"读取媒体流信息 使用时间:{stopwatch.ElapsedMilliseconds}");

        // 这里只是为了打印些视频参数
        //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++)
        {
            if (pFormatContext->streams[i]->codec->codec_type == AVMediaType.AVMEDIA_TYPE_VIDEO)
            {
                streamIndex = i;
                pStream = pFormatContext->streams[i];

            }
            else if (pFormatContext->streams[i]->codec->codec_type == AVMediaType.AVMEDIA_TYPE_AUDIO)
            {
                aStream = pFormatContext->streams[i];

            }
        }
        if (streamIndex == -1 || pStream == null) throw new ApplicationException(@"Could not found video stream.");


        if (error < 0) throw new ApplicationException(GetErrorMessage(error));

        // 获取流的编码器上下文
        var codecContext = *pStream->codec;

        //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对象:用于图像的缩放和转换操作
        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
        pConvertedFrame = ffmpeg.av_frame_alloc();
        // 目标媒体格式需要的字节长度
        var convertedFrameBufferSize = ffmpeg.av_image_get_buffer_size(destinationPixFmt, width, height, 1);
        // 分配目标媒体格式内存使用
        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.");

        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);

        // 丢弃所有的非关键帧
        pCodecContext->skip_frame = AVDiscard.AVDISCARD_NONKEY;

        // 初始化媒体数据包
        var pPacket = new AVPacket();
        packet = &pPacket;
        var frameNumber = 0;
        while (CanRun)
        {
            lock (objLock)
            {
                if (!_isPause)
                {
                    try
                    {
                        do
                        {
                            // 读取一帧未解码数据
                            error = ffmpeg.av_read_frame(pFormatContext, packet);

                            if (error == ffmpeg.AVERROR_EOF) break;
                            if (error < 0) throw new ApplicationException(GetErrorMessage(error));

                            if (packet->stream_index != pStream->index) continue;

                            // 解码
                            error = ffmpeg.avcodec_send_packet(pCodecContext, packet);
                            if (error < 0) throw new ApplicationException(GetErrorMessage(error));

                            // 解码输出解码数据
                            error = ffmpeg.avcodec_receive_frame(pCodecContext, pConvertedFrame);

                        } while (error == ffmpeg.AVERROR(ffmpeg.EAGAIN) && CanRun);

                        if (error == ffmpeg.AVERROR_EOF) continue;
                        if (error < 0) throw new ApplicationException(GetErrorMessage(error));

                        if (packet->stream_index != pStream->index) continue;

                        // YUV->RGB
                        ffmpeg.sws_scale(pConvertContext, pConvertedFrame->data, pConvertedFrame->linesize, 0, height, dstData, dstLinesize);
                    }
                    finally
                    {
                        ffmpeg.av_packet_unref(packet);//释放数据包对象引用
                        ffmpeg.av_frame_unref(pConvertedFrame);//释放解码帧对象引用
                    }

                    // 封装Bitmap图片
                    var bitmap = new Bitmap(width, height, dstLinesize[0], PixelFormat.Format24bppRgb, convertedFrameBufferPtr);
                    // 回调
                    show(bitmap);
                    frameNumber++;

                }
            }
        }


        //播放完置空播放图片 
        show(null);
        Thread.Sleep(200);
        #region 释放资源

        Marshal.FreeHGlobal(convertedFrameBufferPtr);
        ffmpeg.sws_freeContext(pConvertContext);

        ffmpeg.av_free(pConvertedFrame);
        ffmpeg.avcodec_close(pCodecContext);
        ffmpeg.avformat_close_input(&pFormatContextTemp);

        #endregion

        #endregion
    }
    catch (Exception ex)
    {
        strResult = "-1";
        //_log.Error($"解码异常,详细信息:{ex.Message}");
    }
}

暂停的时候将_isPause=true,继续播放的时候设置_isPause=false。


/// <summary>
/// 暂停
/// </summary>
public void Pause()
{
    if (_isPause)
        return;
    lock (objLock)
    {
        _isPause = true;
        Task.Run(() => { Thread.Sleep(20); ffmpeg.av_read_pause(pFormatContext); });

        //ffmpeg.av_packet_unref(packet);
        //ffmpeg.av_frame_unref(pConvertedFrame);//释放解码帧对象引用                
    }
}

/// <summary>
/// 继续播放
/// </summary>
public void ContinuePlay()
{
    if (!_isPause)
        return;
    Task.Run(() =>
    {
        ffmpeg.av_read_play(pFormatContext);
        Thread.Sleep(100);
        _isPause = false;
    });

}

暂停和继续播放方法中用了task多线程,因为不用的话在暂停或继续播放时偶尔会卡死。

FFmpeg.AutoGen库是一个C#封装的FFmpeg库,它可以帮助你在C#应用程序中使用FFmpeg功能。以下是一些使用FFmpeg.AutoGen库的步骤: 1. 下载FFmpeg.AutoGen库 你可以从GitHub上下载FFmpeg.AutoGen库。下载后,将其添加到你的项目中。 2. 导入FFmpeg.AutoGen命名空间 在你的代码中,使用以下语句导入FFmpeg.AutoGen命名空间: ``` using FFmpeg.AutoGen; ``` 3. 初始化FFmpeg 在使用FFmpeg之前,你需要先初始化它。可以使用以下代码: ``` ffmpeg.av_register_all(); ffmpeg.avcodec_register_all(); ffmpeg.avformat_network_init(); ``` 4. 打开输入文件 使用avformat_open_input函数打开输入文件,例如: ``` AVFormatContext* pFormatContext = null; if (ffmpeg.avformat_open_input(&pFormatContext, inputFile, null, null) != 0) { // 出错处理 } ``` 5. 查找信息 使用avformat_find_stream_info函数查找信息,例如: ``` if (ffmpeg.avformat_find_stream_info(pFormatContext, null) < 0) { // 出错处理 } ``` 6. 处理 处理音视频,例如: ``` AVCodecContext* pCodecContext = null; int audioStreamIndex = -1; int videoStreamIndex = -1; for (int i = 0; i < pFormatContext->nb_streams; i++) { AVStream* stream = pFormatContext->streams[i]; AVCodecParameters* codecParameters = stream->codecpar; AVCodec* codec = null; if (codecParameters->codec_type == AVMediaType.AVMEDIA_TYPE_AUDIO) { audioStreamIndex = i; codec = ffmpeg.avcodec_find_decoder(codecParameters->codec_id); pCodecContext = ffmpeg.avcodec_alloc_context3(codec); ffmpeg.avcodec_parameters_to_context(pCodecContext, codecParameters); if (ffmpeg.avcodec_open2(pCodecContext, codec, null) < 0) { // 出错处理 } } else if (codecParameters->codec_type == AVMediaType.AVMEDIA_TYPE_VIDEO) { videoStreamIndex = i; codec = ffmpeg.avcodec_find_decoder(codecParameters->codec_id); pCodecContext = ffmpeg.avcodec_alloc_context3(codec); ffmpeg.avcodec_parameters_to_context(pCodecContext, codecParameters); if (ffmpeg.avcodec_open2(pCodecContext, codec, null) < 0) { // 出错处理 } } else { // 其他类型 } } ``` 7. 释放资源 在使用完FFmpeg后,你需要释放资源。可以使用以下代码: ``` ffmpeg.avcodec_free_context(&pCodecContext); ffmpeg.avformat_close_input(&pFormatContext); ``` 以上是使用FFmpeg.AutoGen库的简单步骤,你可以根据自己的需求进行调整。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值