前面FFmpeg 从零开始开发简单的音视频播放器(五)中,我们已经完成了一个没有声音的纯视频播放器,现在我们要在该视频播放器的基础上进行改造,添加音频播放功能。通过上一节FFmpeg 从零开始开发简单的音视频播放器(六)的开发,我们已经完成了c++部分的音频解码功能,现在就接将它整合到视频播放器中。
在头文件中,添加以下函数接口:
//url:文件地址
extern "C" _declspec(dllexport) int init_ffmpeg(char* url);
//读取一帧 -1:未取到 1:音频 2:视频
extern "C" _declspec(dllexport) int read_frame();
//获取音频帧
extern "C" _declspec(dllexport) char *get_audio_frame();
//获取视频帧
extern "C" _declspec(dllexport) char *get_video_frame();
//获取音频缓存大小
extern "C" _declspec(dllexport) int get_audio_buffer_size();
//获取视频缓存大小
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();
1、右击c++项目-->属性-->配置属性-->常规-->项目默认值-->配置类型-->选择动态库,操作如下图所示:
-----------------------------------------------------------------------------------------
2、添加NAudio播放组件:右击c#项目-->管理NuGet程序包-->浏览中输入“NAudio”-->下载并安装
-------------------------------------------------------------------------------
------------------------------------------------------------------------------
------------------------------------------------------------------------------
3、编写wpf后台代码
using NAudio.Wave;
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_audio_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_audio_buffer_size();
[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;
//缓存时间(ms)
const int BUFFER_DURATION = 2000;
//NAudio音频播放组件
private WaveOut waveOut;
private BufferedWaveProvider bufferedWaveProvider;
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
init();
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;
}
}
///
/// 做一些初始化操作
///
private void init()
{
waveOut = new WaveOut();
bufferedWaveProvider = new BufferedWaveProvider(new WaveFormat());
waveOut.Init(bufferedWaveProvider);
waveOut.Play();
}
///
/// 播放
///
void play()
{
isPlaying = true;
bool isInitOk = false;
while (isPlaying && !isInitOk)
{
//初始化ffmpeg
int initFfmpeg = init_ffmpeg(mallocIntPtr("rtmp://live.hkstv.hk.lxdns.com/live/hks"));
if (initFfmpeg >= 0)
{
isInitOk = true;
break;
}
}
TimeSpan audioMaxBufferedDuration = new TimeSpan(0, 0, 0, 0, BUFFER_DURATION); //音频最大缓冲时间
while (isPlaying)
{
int i = read_frame(); //读取一帧 1:音频 2:视频
if (i == 1)
{
if (bufferedWaveProvider.BufferedDuration.CompareTo(audioMaxBufferedDuration) > 0)
{
bufferedWaveProvider.ClearBuffer();
}
IntPtr audioFrame = get_audio_frame(); //一帧音频句柄
int bufSize = get_audio_buffer_size(); //一帧音频占用大小
byte[] bytes = new byte[bufSize];
Marshal.Copy(audioFrame, bytes, 0, bufSize); //拷贝句柄中的音频到bytes
bufferedWaveProvider.AddSamples(bytes, 0, bufSize);//向缓存中添加音频样本
}
else if (i == 2)
{
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);
Thread t = new Thread(new ParameterizedThreadStart(playVideo));
t.Start(bs);
}));
}
}
try
{
release();
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
///
/// 播放视频,单独开线程,是为了和音频有同步的缓存时间
///
void playVideo(object bs)
{
int time = BUFFER_DURATION - 300;
if (time > 0)
{
Thread.Sleep(time);
}
Dispatcher.BeginInvoke(new Action(delegate { videoImg.Source = bs as BitmapSource; }));
}
///
/// 将字符串分配到非托管区,并返回该非托管区句柄
///
/// 字符串
/// 非托管区句柄
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;
}
}
}
代码说明:
4、拷贝第三步生成的dll文件到执行目录下:
---------------------------------------------------------------------------------
5、重新生成并运行c#项目,运行效果如下:
好了,带上耳机,感受一下成果吧。
注意:由于官方编译的新版本FFmpeg已经不支持xp了,如果有的同学希望在xp下运行播放器,需要下载一个“FixFFmpeg”工具,对FFmpeg的函数做一个映射。
从零开始开发简单的音视频播放器就此完结,第一次接触FFmpeg,有不足的地方,大家多多提点。