在上兩篇文章中已經將播放視頻的功能實作了,今天我就來講解一下如何通過FFmpeg來決議音頻內容,并且用NAudio來進行音頻播放;
效果圖
雖然效果圖是gif并不能
聽到音頻播放的內容,不過可以從圖中看到已經是實作了音頻的播放,暫停,停止已經更改進度的內容了;
一,添加NAudio庫:

一.音頻解碼播放流程
可以從流程圖中看到音頻的解碼跟視頻的解碼是差不多的,只有是重采樣跟將幀資料轉換成位元組陣列這兩個步驟有區別而已,
1.初始化音頻解碼
public void InitDecodecAudio(string path) { int error = 0; //創建一個 媒體格式背景關系 format = ffmpeg.avformat_alloc_context(); var tempFormat = format; //打開媒體檔案 error = ffmpeg.avformat_open_input(&tempFormat, path, null, null); if (error < 0) { Debug.WriteLine("打開媒體檔案失敗"); return; } //嗅探媒體資訊 ffmpeg.avformat_find_stream_info(format, null); AVCodec* codec; //獲取音頻流索引 audioStreamIndex = ffmpeg.av_find_best_stream(format, AVMediaType.AVMEDIA_TYPE_AUDIO, -1, -1, &codec, 0); if (audioStreamIndex < 0) { Debug.WriteLine("沒有找到音頻流"); return; } //獲取音頻流 audioStream = format->streams[audioStreamIndex]; //創建解碼背景關系 codecContext = ffmpeg.avcodec_alloc_context3(codec); //將音頻流里面的解碼器引數設定到 解碼器背景關系中 error = ffmpeg.avcodec_parameters_to_context(codecContext, audioStream->codecpar); if (error < 0) { Debug.WriteLine("設定解碼器引數失敗"); } error = ffmpeg.avcodec_open2(codecContext, codec, null); //媒體時長 Duration = TimeSpan.FromMilliseconds(format->duration / 1000); //編解碼id CodecId = codec->id.ToString(); //解碼器名字 CodecName = ffmpeg.avcodec_get_name(codec->id); //位元率 Bitrate = codecContext->bit_rate; //音頻通道數 Channels = codecContext->channels; //通道布局型別 ChannelLyaout = codecContext->channel_layout; //音頻采樣率 SampleRate = codecContext->sample_rate; //音頻采樣格式 SampleFormat = codecContext->sample_fmt; //采樣次數 //獲取給定音頻引數所需的緩沖區大小, BitsPerSample = ffmpeg.av_samples_get_buffer_size(null, 2, codecContext->frame_size, AVSampleFormat.AV_SAMPLE_FMT_S16, 1); //創建一個指標 audioBuffer = Marshal.AllocHGlobal((int)BitsPerSample); bufferPtr = (byte*)audioBuffer; //初始化音頻重采樣轉換器 InitConvert((int)ChannelLyaout, AVSampleFormat.AV_SAMPLE_FMT_S16, (int)SampleRate, (int)ChannelLyaout, SampleFormat, (int)SampleRate); //創建一個包和幀指標 packet = ffmpeg.av_packet_alloc(); frame = ffmpeg.av_frame_alloc(); State = MediaState.Read; }InitDecodecAudio
在初始化各個結構的代碼其實是跟解碼視頻的流程差不多的,只是獲取媒體流的型別從視頻型別更改成了音頻型別,和媒體的資訊從視頻改為了音頻資訊,并且獲取了音頻的采樣次數和創建了一個用于后續讀取音頻資料的指標,
2.初始化音頻重采樣轉換器,
/// <summary> /// 初始化重采樣轉換器 /// </summary> /// <param name="occ">輸出的通道型別</param> /// <param name="osf">輸出的采樣格式</param> /// <param name="osr">輸出的采樣率</param> /// <param name="icc">輸入的通道型別</param> /// <param name="isf">輸入的采樣格式</param> /// <param name="isr">輸入的采樣率</param> /// <returns></returns> bool InitConvert(int occ, AVSampleFormat osf, int osr, int icc, AVSampleFormat isf, int isr) { //創建一個重采樣轉換器 convert = ffmpeg.swr_alloc(); //設定重采樣轉換器引數 convert = ffmpeg.swr_alloc_set_opts(convert, occ, osf, osr, icc, isf, isr, 0, null); if (convert == null) return false; //初始化重采樣轉換器 ffmpeg.swr_init(convert); return true; }InitConvert
根據輸入引數初始化了一個 SwrContext結構的音頻轉換器,跟 視頻的SwsContext結構是不同的,
3,從音頻幀讀取資料,
public byte[] FrameConvertBytes(AVFrame* sourceFrame) { var tempBufferPtr = bufferPtr; //重采樣音頻 var outputSamplesPerChannel = ffmpeg.swr_convert(convert, &tempBufferPtr, frame->nb_samples, sourceFrame->extended_data, sourceFrame->nb_samples); //獲取重采樣后的音頻資料大小 var outPutBufferLength = ffmpeg.av_samples_get_buffer_size(null, 2, outputSamplesPerChannel, AVSampleFormat.AV_SAMPLE_FMT_S16, 1); if (outputSamplesPerChannel < 0) return null; byte[] bytes = new byte[outPutBufferLength]; //從記憶體中讀取轉換后的音頻資料 Marshal.Copy(audioBuffer, bytes, 0, bytes.Length); return bytes; }FrameConvertBytes
呼叫ffmpeg.swr_convert()將音頻幀通過重采樣后音頻的資料大小會發生改變的,需要再次呼叫 ffmpeg.av_samples_get_buffer_size() 來重新計算 音頻資料大小,并通過指標位置獲取資料;
4.宣告音頻播放組件
//NAudio音頻播放組件 private WaveOut waveOut; private BufferedWaveProvider bufferedWaveProvider;
5.在執行緒任務上回圈讀取音頻幀解碼成位元組陣列并 向bufferedAveProvider 添加音頻樣本,當添加的音頻資料大于預設的資料量則將快取內的資料都清除掉,
PlayTask = new Task(() => { while (true) { //播放中 if (audio.IsPlaying) { //獲取下一幀視頻 if (audio.TryReadNextFrame(out var frame)) { var bytes = audio.FrameConvertBytes(&frame); if (bytes == null) continue; if (bufferedWaveProvider.BufferLength <= bufferedWaveProvider.BufferedBytes+bytes.Length) { bufferedWaveProvider.ClearBuffer(); } bufferedWaveProvider.AddSamples(bytes, 0, bytes.Length);//向快取中添加音頻樣本 } } } }); PlayTask.Start();PlayTask
二.音頻讀取解碼整個流程 DecodecAudio 類
public unsafe class DecodecAudio : IMedia { //媒體格式容器 AVFormatContext* format; //解碼背景關系 AVCodecContext* codecContext; AVStream* audioStream; //媒體資料包 AVPacket* packet; AVFrame* frame; SwrContext* convert; int audioStreamIndex; bool isNextFrame = true; //播放上一幀的時間 TimeSpan lastTime; TimeSpan OffsetClock; object SyncLock = new object(); Stopwatch clock = new Stopwatch(); bool isNexFrame = true; public event IMedia.MediaHandler MediaCompleted; //是否是正在播放中 public bool IsPlaying { get; protected set; } /// <summary> /// 媒體狀態 /// </summary> public MediaState State { get; protected set; } /// <summary> /// 幀播放時長 /// </summary> public TimeSpan frameDuration { get; protected set; } /// <summary> /// 媒體時長 /// </summary> public TimeSpan Duration { get; protected set; } /// <summary> /// 播放位置 /// </summary> public TimeSpan Position { get => OffsetClock + clock.Elapsed; } /// <summary> /// 解碼器名字 /// </summary> public string CodecName { get; protected set; } /// <summary> /// 解碼器Id /// </summary> public string CodecId { get; protected set; } /// <summary> /// 位元率 /// </summary> public long Bitrate { get; protected set; } //通道數 public int Channels { get; protected set; } //采樣率 public long SampleRate { get; protected set; } //采樣次數 public long BitsPerSample { get; protected set; } //通道布局 public ulong ChannelLyaout { get; protected set; } /// <summary> /// 采樣格式 /// </summary> public AVSampleFormat SampleFormat { get; protected set; } public void InitDecodecAudio(string path) { int error = 0; //創建一個 媒體格式背景關系 format = ffmpeg.avformat_alloc_context(); var tempFormat = format; //打開媒體檔案 error = ffmpeg.avformat_open_input(&tempFormat, path, null, null); if (error < 0) { Debug.WriteLine("打開媒體檔案失敗"); return; } //嗅探媒體資訊 ffmpeg.avformat_find_stream_info(format, null); AVCodec* codec; //獲取音頻流索引 audioStreamIndex = ffmpeg.av_find_best_stream(format, AVMediaType.AVMEDIA_TYPE_AUDIO, -1, -1, &codec, 0); if (audioStreamIndex < 0) { Debug.WriteLine("沒有找到音頻流"); return; } //獲取音頻流 audioStream = format->streams[audioStreamIndex]; //創建解碼背景關系 codecContext = ffmpeg.avcodec_alloc_context3(codec); //將音頻流里面的解碼器引數設定到 解碼器背景關系中 error = ffmpeg.avcodec_parameters_to_context(codecContext, audioStream->codecpar); if (error < 0) { Debug.WriteLine("設定解碼器引數失敗"); } error = ffmpeg.avcodec_open2(codecContext, codec, null); //媒體時長 Duration = TimeSpan.FromMilliseconds(format->duration / 1000); //編解碼id CodecId = codec->id.ToString(); //解碼器名字 CodecName = ffmpeg.avcodec_get_name(codec->id); //位元率 Bitrate = codecContext->bit_rate; //音頻通道數 Channels = codecContext->channels; //通道布局型別 ChannelLyaout = codecContext->channel_layout; //音頻采樣率 SampleRate = codecContext->sample_rate; //音頻采樣格式 SampleFormat = codecContext->sample_fmt; //采樣次數 //獲取給定音頻引數所需的緩沖區大小, BitsPerSample = ffmpeg.av_samples_get_buffer_size(null, 2, codecContext->frame_size, AVSampleFormat.AV_SAMPLE_FMT_S16, 1); //創建一個指標 audioBuffer = Marshal.AllocHGlobal((int)BitsPerSample); bufferPtr = (byte*)audioBuffer; //初始化音頻重采樣轉換器 InitConvert((int)ChannelLyaout, AVSampleFormat.AV_SAMPLE_FMT_S16, (int)SampleRate, (int)ChannelLyaout, SampleFormat, (int)SampleRate); //創建一個包和幀指標 packet = ffmpeg.av_packet_alloc(); frame = ffmpeg.av_frame_alloc(); State = MediaState.Read; } //緩沖區指標 IntPtr audioBuffer; //緩沖區句柄 byte* bufferPtr; /// <summary> /// 初始化重采樣轉換器 /// </summary> /// <param name="occ">輸出的通道型別</param> /// <param name="osf">輸出的采樣格式</param> /// <param name="osr">輸出的采樣率</param> /// <param name="icc">輸入的通道型別</param> /// <param name="isf">輸入的采樣格式</param> /// <param name="isr">輸入的采樣率</param> /// <returns></returns> bool InitConvert(int occ, AVSampleFormat osf, int osr, int icc, AVSampleFormat isf, int isr) { //創建一個重采樣轉換器 convert = ffmpeg.swr_alloc(); //設定重采樣轉換器引數 convert = ffmpeg.swr_alloc_set_opts(convert, occ, osf, osr, icc, isf, isr, 0, null); if (convert == null) return false; //初始化重采樣轉換器 ffmpeg.swr_init(convert); return true; } /// <summary> /// 嘗試讀取下一幀 /// </summary> /// <param name="outFrame"></param> /// <returns></returns> public bool TryReadNextFrame(out AVFrame outFrame) { if (lastTime == TimeSpan.Zero) { lastTime = Position; isNextFrame = true; } else { if (Position - lastTime >= frameDuration) { lastTime = Position; isNextFrame = true; } else { outFrame = *frame; return false; } } if (isNextFrame) { lock (SyncLock) { int result = -1; //清理上一幀的資料 ffmpeg.av_frame_unref(frame); while (true) { //清理上一幀的資料包 ffmpeg.av_packet_unref(packet); //讀取下一幀,回傳一個int 查看讀取資料包的狀態 result = ffmpeg.av_read_frame(format, packet); //讀取了最后一幀了,沒有資料了,退出讀取幀 if (result == ffmpeg.AVERROR_EOF || result < 0) { outFrame = *frame; StopPlay(); return false; } //判斷讀取的幀資料是否是視頻資料,不是則繼續讀取 if (packet->stream_index != audioStreamIndex) continue; //將包資料發送給解碼器解碼 ffmpeg.avcodec_send_packet(codecContext, packet); //從解碼器中接收解碼后的幀 result = ffmpeg.avcodec_receive_frame(codecContext, frame); if (result < 0) continue; //計算當前幀播放的時長 frameDuration = TimeSpan.FromTicks((long)Math.Round(TimeSpan.TicksPerMillisecond * 1000d * frame->nb_samples / frame->sample_rate, 0)); outFrame = *frame; return true; } } } else { outFrame = *frame; return false; } } void StopPlay() { lock (SyncLock) { if (State == MediaState.None) return; IsPlaying = false; OffsetClock = TimeSpan.FromSeconds(0); clock.Reset(); clock.Stop(); var tempFormat = format; ffmpeg.avformat_free_context(tempFormat); format = null; var tempCodecContext = codecContext; ffmpeg.avcodec_free_context(&tempCodecContext); var tempPacket = packet; ffmpeg.av_packet_free(&tempPacket); var tempFrame = frame; ffmpeg.av_frame_free(&tempFrame); var tempConvert = convert; ffmpeg.swr_free(&tempConvert); Marshal.FreeHGlobal(audioBuffer); bufferPtr = null; audioStream = null; audioStreamIndex = -1; //視頻時長 Duration = TimeSpan.FromMilliseconds(0); //編解碼器名字 CodecName = String.Empty; CodecId = String.Empty; //位元率 Bitrate = 0; //幀率 Channels = 0; ChannelLyaout = 0; SampleRate = 0; BitsPerSample = 0; State = MediaState.None; lastTime = TimeSpan.Zero; MediaCompleted?.Invoke(Duration); } } /// <summary> /// 更改進度 /// </summary> /// <param name="seekTime">更改到的位置(秒)</param> public void SeekProgress(int seekTime) { if (format == null || audioStreamIndex == null) return; lock (SyncLock) { IsPlaying = false;//將視頻暫停播放 clock.Stop(); //將秒數轉換成視頻的時間戳 var timestamp = seekTime / ffmpeg.av_q2d(audioStream->time_base); //將媒體容器里面的指定流(視頻)的時間戳設定到指定的位置,并指定跳轉的方法; ffmpeg.av_seek_frame(format, audioStreamIndex, (long)timestamp, ffmpeg.AVSEEK_FLAG_BACKWARD | ffmpeg.AVSEEK_FLAG_FRAME); ffmpeg.av_frame_unref(frame);//清除上一幀的資料 ffmpeg.av_packet_unref(packet); //清除上一幀的資料包 int error = 0; //回圈獲取幀資料,判斷獲取的幀時間戳已經大于給定的時間戳則說明已經到達了指定的位置則退出回圈 while (packet->pts < timestamp) { do { do { ffmpeg.av_packet_unref(packet);//清除上一幀資料包 error = ffmpeg.av_read_frame(format, packet);//讀取資料 if (error == ffmpeg.AVERROR_EOF)//是否是到達了視頻的結束位置 return; } while (packet->stream_index != audioStreamIndex);//判斷當前獲取的資料是否是視頻資料 ffmpeg.avcodec_send_packet(codecContext, packet);//將資料包發送給解碼器解碼 error = ffmpeg.avcodec_receive_frame(codecContext, frame);//從解碼器獲取解碼后的幀資料 } while (error == ffmpeg.AVERROR(ffmpeg.EAGAIN)); } OffsetClock = TimeSpan.FromSeconds(seekTime);//設定時間偏移 clock.Restart();//時鐘從新開始 IsPlaying = true;//視頻開始播放 lastTime = TimeSpan.Zero; } } /// <summary> /// 將音頻幀轉換成位元組陣列 /// </summary> /// <param name="sourceFrame"></param> /// <returns></returns> public byte[] FrameConvertBytes(AVFrame* sourceFrame) { var tempBufferPtr = bufferPtr; //重采樣音頻 var outputSamplesPerChannel = ffmpeg.swr_convert(convert, &tempBufferPtr, frame->nb_samples, sourceFrame->extended_data, sourceFrame->nb_samples); //獲取重采樣后的音頻資料大小 var outPutBufferLength = ffmpeg.av_samples_get_buffer_size(null, 2, outputSamplesPerChannel, AVSampleFormat.AV_SAMPLE_FMT_S16, 1); if (outputSamplesPerChannel < 0) return null; byte[] bytes = new byte[outPutBufferLength]; //從記憶體中讀取轉換后的音頻資料 Marshal.Copy(audioBuffer, bytes, 0, bytes.Length); return bytes; } public void Play() { if (State == MediaState.Play) return; clock.Start(); IsPlaying = true; State = MediaState.Play; } public void Pause() { if (State != MediaState.Play) return; IsPlaying = false; OffsetClock = clock.Elapsed; clock.Stop(); clock.Reset(); State = MediaState.Pause; } public void Stop() { if (State == MediaState.None) return; StopPlay(); } }DecodecAudio
三.解碼布局,和后臺代碼
<Grid>
<Grid.Resources>
<Style TargetType="TextBlock" x:Key="Key">
<Setter Property="control:DockPanel.Dock" Value=https://www.cnblogs.com/chifan/archive/2022/07/25/"Left" />
<Setter Property="HorizontalAlignment" Value=https://www.cnblogs.com/chifan/archive/2022/07/25/"Left" />
<Setter Property="FontWeight" Value=https://www.cnblogs.com/chifan/archive/2022/07/25/"Bold" />
<Setter Property="FontSize" Value=https://www.cnblogs.com/chifan/archive/2022/07/25/"15"></Setter>
<Setter Property="Foreground" Value=https://www.cnblogs.com/chifan/archive/2022/07/25/"White"></Setter>
<Setter Property="Width" Value=https://www.cnblogs.com/chifan/archive/2022/07/25/"150" />
</Style>
<Style TargetType="TextBlock" x:Key="Value">
<Setter Property="control:DockPanel.Dock" Value=https://www.cnblogs.com/chifan/archive/2022/07/25/"Right" />
<Setter Property="HorizontalAlignment" Value=https://www.cnblogs.com/chifan/archive/2022/07/25/"Stretch" />
<Setter Property="FontWeight" Value=https://www.cnblogs.com/chifan/archive/2022/07/25/"Normal" />
<Setter Property="FontSize" Value=https://www.cnblogs.com/chifan/archive/2022/07/25/"15"></Setter>
<Setter Property="Foreground" Value=https://www.cnblogs.com/chifan/archive/2022/07/25/"White"></Setter>
</Style>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition Height="auto"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition ></ColumnDefinition>
<ColumnDefinition Width="auto"></ColumnDefinition>
</Grid.ColumnDefinitions>
<canvas:CanvasControl x:Name="canvas"></canvas:CanvasControl>
<StackPanel Background="Black" Grid.Column="1" Width="300">
<control:DockPanel>
<TextBlock Text="Duration" Style="{StaticResource Key}"></TextBlock>
<TextBlock x:Name="dura" Text="00:00:00" Style="{StaticResource Value}"></TextBlock>
</control:DockPanel>
<control:DockPanel>
<TextBlock Text="Position" Style="{StaticResource Key}"></TextBlock>
<TextBlock x:Name="position" Text="00:00:00" Style="{StaticResource Value}"></TextBlock>
</control:DockPanel>
<control:DockPanel Background="LightBlue">
<TextBlock Style="{StaticResource Key}">Has Video</TextBlock>
<TextBlock Style="{StaticResource Value}" />
</control:DockPanel>
<control:DockPanel >
<TextBlock Style="{StaticResource Key}" Text="Audi Codec"></TextBlock>
<TextBlock Style="{StaticResource Value}" x:Name="audioCodec" />
</control:DockPanel>
<control:DockPanel >
<TextBlock Style="{StaticResource Key}" Text="Audio Bitrate"></TextBlock>
<TextBlock Style="{StaticResource Value}" x:Name="audioBitrate" />
</control:DockPanel>
<control:DockPanel >
<TextBlock Style="{StaticResource Key}" Text="Audio Channels"></TextBlock>
<TextBlock Style="{StaticResource Value}" x:Name="audioChannels"/>
</control:DockPanel>
<control:DockPanel >
<TextBlock Style="{StaticResource Key}" Text="Audio ChannelsLayout"></TextBlock>
<TextBlock Style="{StaticResource Value}" x:Name="audioChannelsLayout"/>
</control:DockPanel>
<control:DockPanel >
<TextBlock Style="{StaticResource Key}" Text="Audio SampleRate"></TextBlock>
<TextBlock Style="{StaticResource Value}" x:Name="audioSampleRate" />
</control:DockPanel>
<control:DockPanel >
<TextBlock Style="{StaticResource Key}" Text="Audio BitsPerSample"></TextBlock>
<TextBlock Style="{StaticResource Value}" x:Name="audioBitsPerSample" />
</control:DockPanel>
</StackPanel>
<StackPanel Grid.Row="1" Grid.ColumnSpan="2">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"></ColumnDefinition>
<ColumnDefinition ></ColumnDefinition>
<ColumnDefinition Width="auto"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding ElementName=position,Path=Text,Mode=OneWay}"></TextBlock>
<Slider Grid.Column="1" x:Name="progress"></Slider>
<TextBlock Grid.Column="2" Text="00:00:00" x:Name="duration"></TextBlock>
</Grid>
<TextBox x:Name="pathBox" Text="C:\Users\ludin\Desktop\新建檔案夾 (4)\2.mp3" PlaceholderText="地址輸入"></TextBox>
<StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
<Button x:Name="play" >播放</Button>
<Button x:Name="pause" >暫停</Button>
<Button x:Name="stop" >停止</Button>
</StackPanel>
</StackPanel>
</Grid>
Xaml
Task PlayTask; CanvasBitmap bitmap; DispatcherTimer timer = new DispatcherTimer(); bool progressActivity = false; DecodecAudio audio = new DecodecAudio(); //NAudio音頻播放組件 private WaveOut waveOut; private BufferedWaveProvider bufferedWaveProvider; public FFmpegDecodecAudio() { this.InitializeComponent(); Init(); InitUi(); } void Init() { waveOut = new WaveOut(); bufferedWaveProvider = new BufferedWaveProvider(new WaveFormat()); waveOut.Init(bufferedWaveProvider); waveOut.Play(); //播放 play.Click += (s, e) => { if (audio.State == MediaState.None) { //初始化解碼視頻 audio.InitDecodecAudio(pathBox.Text); DisplayVideoInfo(); } audio.Play(); timer.Start(); }; //暫停 pause.Click += (s, e) => audio.Pause(); stop.Click += (s, e) => audio.Stop(); ; PlayTask = new Task(() => { while (true) { //播放中 if (audio.IsPlaying) { //獲取下一幀視頻 if (audio.TryReadNextFrame(out var frame)) { var bytes = audio.FrameConvertBytes(&frame); if (bytes == null) continue; if (bufferedWaveProvider.BufferLength <= bufferedWaveProvider.BufferedBytes+bytes.Length) { bufferedWaveProvider.ClearBuffer(); } bufferedWaveProvider.AddSamples(bytes, 0, bytes.Length);//向快取中添加音頻樣本 } } } }); PlayTask.Start(); audio.MediaCompleted += (s) => { DispatcherQueue.TryEnqueue(Microsoft.UI.Dispatching.DispatcherQueuePriority.Normal, () => { timer.Stop(); progressActivity = false; DisplayVideoInfo(); }); }; } void InitUi() { //畫布繪制 canvas.Draw += (s, e) => { if (bitmap != null) { var te = Win2DUlit.CalcutateImageCenteredTransform(canvas.ActualSize, bitmap.Size); te.Source = bitmap; e.DrawingSession.DrawImage(te); } }; timer.Interval = TimeSpan.FromMilliseconds(300); //計時器更新進度條 timer.Tick += (s, e) => { if (!audio.IsPlaying) return; position.Text = audio.Position.ToString(); progressActivity = false; progress.Value = audio.Position.TotalSeconds; progressActivity = true; }; //進度條更改 progress.ValueChanged += (s, e) => { if (!audio.IsPlaying) return; if (progressActivity == true) { audio.SeekProgress((int)e.NewValue); } }; } /// <summary> /// 顯示視頻資訊 /// </summary> void DisplayVideoInfo() { dura.Text = audio.Duration.ToString(); audioCodec.Text = audio.CodecName; audioBitrate.Text = audio.Bitrate.ToString(); audioChannels.Text = audio.Channels.ToString(); audioChannelsLayout .Text = audio.ChannelLyaout.ToString(); audioSampleRate.Text = audio.SampleRate.ToString(); audioBitsPerSample.Text = audio.BitsPerSample.ToString(); position.Text = audio.Position.ToString(); progress.Maximum = audio.Duration.TotalSeconds; }View Code
四.結語
在整個解碼和播放音頻的程序中跟視頻解碼播放大致上是一樣的,只有是重采樣和讀取資料的方式不一樣而已,通過上面的代碼就可以簡單的實作音頻播放,暫停,停止和更改進度的功能了
感興趣的朋友可以到專案demo上了解運行情況,
專案Demo地址:LearnFFmppeg: 學習和記錄ffmpeg - Gitee.com
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/500267.html
標籤:.NET技术
上一篇:C#Socket
