今天整理到音频播放的部分,本来就想抽取一个简单的接口方便以后可能会用到,然而不知不觉就把常用的功能都给一起封装好了,实现只需一行代码就实现音频播放,核心其实就是调用MCI的API接口,具体的功能就是变换不同的MCI指令来实现。
========== 原创作品 作者:未闻 出处:博客园 ==========
一、常见的音频播放方式
* System.Media.SoundPlayer:播放wav
* MCI Command String:播放MP3、AVI等
* axWindowsMediaPlayer:COM组件,功能丰富易用
二、 注意事项
* 应用于窗体程序,不能应用于控制台程序(不知道是不是因为取不到窗体句柄,加Sleep也没用,知道的不妨留言告知)
三、代码
封装好的类,可以直接用了,这里用了单例简化了用法,其实只要别名不一样,还可以支持同时播放多个音频。
using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace System.Media { /// <summary> /// 音频播放器(基于MCI-API接口) /// 作者:未闻 /// 时间:2020.02.13 /// /// 详细的指令介绍 /// https://blog.csdn.net/psongchao/article/details/1487788 /// </summary> public class AudioPlayer { #region API定义 [DllImport("winmm.dll")] static extern int mciSendString(string m_strCmd, StringBuilder m_strReceive, int m_v1, int m_v2); [DllImport("Kernel32", CharSet = CharSet.Auto)] static extern int GetShortPathName(string path, StringBuilder shortPath, int shortPathLength); private void SendCommand(string cmd) { mciSendString(cmd, null, 0, 0); } private string SendCommandForResult(string cmd) { mciSendString(cmd, _temp, _temp.Capacity, 0); return _temp.ToString(); } #endregion public AudioPlayer(string alias) { AliasName = alias; //// 获取声道 //var ret = SendCommandForResult($"status {AliasName} source"); //if (!string.IsNullOrWhiteSpace(ret)) // _source = _sourceMap.FirstOrDefault(pair => pair.Value.Equals(ret)).Key; //// 音频状态,是否静音 //ret = SendCommandForResult($"status {AliasName} audio"); //if (!string.IsNullOrWhiteSpace(ret)) // _audioStatus = _audioStatusMap.FirstOrDefault(pair => pair.Value.Equals(ret)).Key; timer.Tick += Timer_Tick; } #region 单例 class Nested { public static AudioPlayer Instance = new AudioPlayer("AUDIO_PLAYER_SINGLETON"); } public static AudioPlayer Instance => Nested.Instance; #endregion // 播放别名,每个播放源(声音)采用一个别名来识别,可以支持同时播放多个声音 public string AliasName { get; private set; } private StringBuilder _temp = new StringBuilder(260); private Dictionary<AudioSource, string> _sourceMap = new Dictionary<AudioSource, string> { {AudioSource.H, "stereo"}, {AudioSource.A, "average"}, {AudioSource.L, "left"}, {AudioSource.R, "right"} }; private Dictionary<bool, string> _audioStatusMap = new Dictionary<bool, string> { {true, "on"}, {false, "off"} }; private Timer timer = new Timer { Interval = 1000 }; public event Action Progress; public event Action Completed; private void Timer_Tick(object sender, EventArgs e) { if (!IsCompleted) { Progress?.Invoke(); return; } Status = PlayerStatus.Stop; timer.Stop(); Completed?.Invoke(); } /// <summary> /// 准备 /// </summary> /// <param name="fileName"></param> /// <param name="autoPlay">是否自动播放,默认true</param> public void Prepare(string fileName, bool autoPlay = true) { if (Status == PlayerStatus.Playing) Stop(); if (string.IsNullOrWhiteSpace(fileName)) return; GetShortPathName(fileName, _temp, _temp.Capacity); var mp3Path = _temp.ToString(); SendCommand($"open "{mp3Path}" alias {AliasName}"); //打开 if (autoPlay) Play(); // 因为设置静音后一播放,会变成有声音,所以这里要设置一下 AudioStatus = _audioStatus; Source = _source; Volume = _vol; } /// <summary> /// 播放 /// </summary> public void Play() { SendCommand($"play {AliasName}"); Status = PlayerStatus.Playing; timer.Start(); } /// <summary> /// 停止 /// </summary> public void Stop() { SendCommand($"close {AliasName}"); Status = PlayerStatus.Stop; timer.Stop(); } /// <summary> /// 暂停 /// </summary> public void Pause() { SendCommand($"pause {AliasName}"); Status = PlayerStatus.Pause; timer.Stop(); } /// <summary> /// 播放状态 /// </summary> public PlayerStatus Status { get; private set; } = PlayerStatus.Stop; private bool _audioStatus = true; /// <summary> /// 音频状态(true 开启,false 静音) /// </summary> public bool AudioStatus { get => _audioStatus; set { _audioStatus = value; SendCommand($"setaudio {AliasName} {_audioStatusMap[value]}"); } } private AudioSource _source = AudioSource.H; /// <summary> /// 播放声道 /// </summary> public AudioSource Source { get => _source; set { _source = value; SendCommand($"setaudio {AliasName} source to {_sourceMap[value]}"); } } private int _vol = 500; /// <summary> /// 音量 /// </summary> public int Volume { get => _vol; //{ // var ret = SendCommandForResult($"status {AliasName} volume"); // if (string.IsNullOrWhiteSpace(ret)) // return 500; // return Convert.ToInt32(ret); //} set { if (value < 0 || value > 1000) return; _vol = value; SendCommand($"setaudio {AliasName} volume to {value}"); } } /// <summary> /// 获取是否正在播放 /// </summary> public bool IsPlaying => Status == PlayerStatus.Playing; /// <summary> /// 获取是否已播放结束 /// </summary> public bool IsCompleted => Position >= Length; /// <summary> /// 获取播放总时长 /// </summary> public int Length { get { var ret = SendCommandForResult($"status {AliasName} length"); if (string.IsNullOrWhiteSpace(ret)) return 0; return Convert.ToInt32(ret); } } /// <summary> /// 获取播放总时长(格式:00:00) /// </summary> public string LengthString { get { return Len2Time(Length); } } /// <summary> /// 获取播放进度 /// </summary> public int Position { get { var ret = SendCommandForResult($"status {AliasName} position"); if (string.IsNullOrWhiteSpace(ret)) return 0; return Convert.ToInt32(_temp.ToString()); } set { if (value < 0 || value > Length) return; SendCommand($"seek {AliasName} to {value}"); Play(); } } /// <summary> /// 获取播放进度(格式:00:00) /// </summary> public string PositionString { get { return Len2Time(Position); } } /// <summary> /// 把时长从int类型转换成格式为00:00的字符串 /// </summary> /// <param name="len"></param> /// <returns></returns> private string Len2Time(int len) { int sec = len / 1000 % 60; int min = len / 60000 % 60; return string.Format("{0:D2}:{1:D2}", min, sec); } } public enum PlayerStatus { /// <summary> /// 停止 /// </summary> Stop = 0, /// <summary> /// 播放中 /// </summary> Playing = 1, /// <summary> /// 暂停 /// </summary> Pause = 2 } public enum AudioSource { /// <summary> /// 立体声 /// </summary> H = 0, /// <summary> /// 平均声道 /// </summary> A = 1, /// <summary> /// 左声道 /// </summary> L = 2, /// <summary> /// 右声道 /// </summary> R = 3 } }
四、调用示例
AudioPlayer player = AudioPlayer.Instance; public Form1() { InitializeComponent(); player.Progress += Player_Progress; player.Completed += Player_Completed; } private void Player_Completed() { lblName.Text = "暂无曲目"; } private void Player_Progress() { UpdateProgress(); } private void btnOpenFile_Click(object sender, EventArgs e) { if (openFileDialog1.ShowDialog() == DialogResult.OK) { player.Prepare(openFileDialog1.FileName); tbrProgress.Maximum = player.Length; UpdateProgress(); lblName.Text = Path.GetFileName(openFileDialog1.FileName); } } /// <summary> /// 更新当前播放进度 /// </summary> private void UpdateProgress() { lblPos.Text = player.PositionString; lblLen.Text = player.LengthString; tbrProgress.Value = player.Position; }
五、参考资料