FFmpeg 从零开始开发简单的音视频播放器(五)

c#中播放视频

一、简单说明

        上一节,已经完成了视频的解码和转码功能,如果现在经理提了一个需求:要用c#开发一个视频播放器。那么如何才能使用c#,将上一节打印出来的看不懂的像素,显示到页面上呢,下面就一步一步来实现。

二、暴露c++接口

        1、创建头文件:右击c++项目-->添加新建项

FFmpeg 从零开始开发简单的音视频播放器(五)_第1张图片

        2、引入头文件:

FFmpeg 从零开始开发简单的音视频播放器(五)_第2张图片

        3、头文件中,暴露c++中的函数接口:

//url:文件地址
extern "C" _declspec(dllexport) int init_ffmpeg(char* url);

//读取一帧 
extern "C" _declspec(dllexport) int read_frame();

//获取视频帧
extern "C" _declspec(dllexport) char *get_video_frame();

//获取视频缓存大小
extern "C" _declspec(dllexport) int get_video_buffer_size();

//获取视频宽度
extern "C" _declspec(dllexport) int get_video_width();

//获取视频高度
extern "C" _declspec(dllexport) int get_video_height();

//释放资源
extern "C" _declspec(dllexport) void release();

三、将c++项目改造为动态链接库(dll)项目

        1、右击c++项目-->属性-->配置属性-->常规-->项目默认值-->配置类型-->选择动态库,操作如下图所示:

FFmpeg 从零开始开发简单的音视频播放器(五)_第3张图片
           注意:这里采用的是vs默认的Debug配置,平台采用的是vs默认的win32平台。开发的同学可以根据自己的需求,在这个页面进行配置。
        2、重新生成c++项目:
FFmpeg 从零开始开发简单的音视频播放器(五)_第4张图片
        现在项目的生成的已经不再是exe文件了。如上图,动态链接库已经生成完毕。

四、配置c#项目

        在前面的章节中,创建了一个主WPF项目,一直没有使用,现在终于要对它下手了。

        1、用一个image控件来做视频显示,编写简单的xaml页面代码如下:


    
        
    

        2、编写后台代码:

using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace FFmpegPlayer
{
    /// 
    /// MainWindow.xaml 的交互逻辑
    /// 
    public partial class MainWindow : Window
    {
        [DllImport("FFmpegDll.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern int init_ffmpeg(IntPtr url);

        [DllImport("FFmpegDll.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern int read_frame();

        [DllImport("FFmpegDll.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern IntPtr get_video_frame();

        [DllImport("FFmpegDll.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern int get_video_buffer_size();

        [DllImport("FFmpegDll.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern int get_video_width();

        [DllImport("FFmpegDll.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern int get_video_height();

        [DllImport("FFmpegDll.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern void release();

        //播放状态
        private bool isPlaying = false;

        public MainWindow()
        {
            InitializeComponent();
        }

        /// 
        /// 播放
        /// 
        void play()
        {
            isPlaying = true;

            bool isInitOk = false;
            while (isPlaying && !isInitOk)
            {
                int initFfmpeg = init_ffmpeg(mallocIntPtr("rtmp://live.hkstv.hk.lxdns.com/live/hks"));
                if (initFfmpeg >= 0)
                {
                    isInitOk = true;
                    break;
                }
            }

            while (isPlaying)
            {
                int i = read_frame(); //读取一帧
                if (i >= 0) 
                {
                    Dispatcher.Invoke(new Action(delegate
                    {
                        BitmapSource bs = BitmapImage.Create(
                            get_video_width(),
                            get_video_height(),
                            0,
                            0,
                            PixelFormats.Rgb24,
                            null,
                            get_video_frame(),
                            get_video_buffer_size(),
                            (get_video_width() * PixelFormats.Rgb24.BitsPerPixel + 7) / 8);
                        videoImg.Source = bs as BitmapSource;
                    }));
                }
            }

            try
            {
                release();
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
            }
        }

        /// 
        /// 将字符串分配到非托管区,并返回该非托管区句柄
        /// 
        /// 字符串
        /// 非托管区句柄
        private IntPtr mallocIntPtr(String strData)
        {
            //字符串转化为字节
            Byte[] bytesData = System.Text.Encoding.Default.GetBytes(strData);

            //分配非托管区内存
            IntPtr ptr = Marshal.AllocHGlobal(bytesData.Length);

            //清空非托管区
            Byte[] emptyBytes = new Byte[bytesData.Length + 1]; //一定要加1,否则后面是乱码,原因未找到
            Marshal.Copy(emptyBytes, 0, ptr, emptyBytes.Length);

            //往非托管区塞数据
            Marshal.Copy(bytesData, 0, ptr, bytesData.Length);

            return ptr;
        }

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            new Thread(play).Start();
        }

        protected override void OnClosing(CancelEventArgs e)
        {
            MessageBoxResult result = MessageBox.Show("退出播放器吗?", "提示", MessageBoxButton.YesNo);
            if (result.Equals(MessageBoxResult.Yes))
            {
                base.OnClosing(e);
                isPlaying = false;
            }
            else
            {
                e.Cancel = true;
            }
        }
    }
}

        代码说明:

  • 用DllImport的方式,引用c++函数;
  • c#中的IntPtr类型,对应c++的指针;
  • private IntPtr mallocIntPtr(String strData)方法返回的是一个内存块的句柄,在上面代码中,返回的是一个字符串:“rtmp://live.hkstv.hk.lxdns.com/live/hks”所在的内存地址。

        3、将c#项目设置为启动项目:

FFmpeg 从零开始开发简单的音视频播放器(五)_第5张图片

        4、拷贝dll文件到执行目录下:

FFmpeg 从零开始开发简单的音视频播放器(五)_第6张图片

        注意:这里需要拷贝两个地方的dll到执行目录下,一个是上面第三步生成的dll,第二个是官方下载的(或者自己编译生成的)“ffmpeg-3.4.2-win32-shared\bin”下的dll。

        5、重新生成并运行c#项目:

        运行过程中,多半会出现以下异常:

FFmpeg 从零开始开发简单的音视频播放器(五)_第7张图片

        这是由于我们生成的所有dll,都是基于win32平台的,而c#项目的默认配置,并没有指定采用哪种平台进行生成。因此我们要对c#项目进行配置,右击c#项目-->属性-->生成-->常规-->平台目标-->选择x86并保存,如下图所示:

FFmpeg 从零开始开发简单的音视频播放器(五)_第8张图片

        再次重新生成并运行c#项目,运行效果如下:

FFmpeg 从零开始开发简单的音视频播放器(五)_第9张图片

        这样,一个超级简单的视频播放器,就完成了。但是不要高兴的太早,目前的播放器只有画面,还没有声音。如何加入音频,在接下来的章节慢慢说,这节就这样。有不足的地方,大佬们多多指点,也请大家给个赞吧。

你可能感兴趣的:(wpf,c++,FFmpeg,c#)