Construct a video player with SaarFFMPEG (ReadPacket)

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Saar.FFmpeg.CSharp; using System.Drawing; using System.Drawing.Imaging; using System.Windows.Forms; using System.Threading; using Coolest.Windows; using Coolest; using System.IO; namespace SaarFFmpeg.VideoTest { public class Program { public class BufferedWaveStream : IWaveStream { private MemoryStream mStream = new MemoryStream(); private long mReader; private long mWriter; private object mStreamLocker = new object(); public void AddSample(byte[] buffer, int start, int length) { lock (mStreamLocker) { long pos = mStream.Position; mStream.Position = mWriter; mStream.Write(buffer, start, length); mWriter = mStream.Position; mStream.Position = pos; } } unsafe public void AddSample(byte* buffer, int start, int length) { UnmanagedMemoryStream memoryStream = new UnmanagedMemoryStream(buffer + start, length); lock (mStreamLocker) { long pos = mStream.Position; mStream.Position = mWriter; memoryStream.CopyTo(mStream); mWriter = mStream.Position; mStream.Position = pos; } memoryStream.Dispose(); } public WaveFormat Format { get; private set; } public int Read(byte[] buffer, int start, int length) { lock (mStreamLocker) { int ret = 0; long pos = mStream.Position; mStream.Position = mReader; ret = mStream.Read(buffer, start, length); mReader = mStream.Position; mStream.Position = pos; return ret; } } public BufferedWaveStream(WaveFormat waveFormat) { this.Format = waveFormat; } } public class VideoFormPanel : Panel { object locker = new object(); List<Bitmap> mFrames = new List<Bitmap>(); Bitmap mBmp; public VideoFormPanel() { this.DoubleBuffered = true; SetStyle(ControlStyles.OptimizedDoubleBuffer, true); } public void UpdateBitmap(Bitmap bmp) { try { if (!this.IsHandleCreated || this.IsDisposed) return; if (this.InvokeRequired) { this.Invoke(new Action(() => { UpdateBitmap(bmp); })); return; } BMP = bmp; this.Invalidate(); } catch (Exception ee) { Console.WriteLine(ee.ToString()); } } public Bitmap BMP { get { lock (locker) { return mBmp; } } set { lock (locker) { mBmp = value; } } } protected override void OnPaint(PaintEventArgs e) { Bitmap bmp = BMP; if (bmp != null) { e.Graphics.DrawImage(bmp, 0, 0, this.Width, this.Height); } base.OnPaint(e); } } public static AudioFormat AudioFormatFromWaveFormat(Coolest.WaveFormat waveFormat, bool isPlanar = false) { return new AudioFormat(waveFormat.SampleRate, waveFormat.Channels, waveFormat.BitsPerSample, isPlanar); } [STAThread] static void Main(string[] args) { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); OpenFileDialog openFile = new OpenFileDialog(); openFile.Filter = "MP4 File(*.mp4)|*.mp4"; openFile.FilterIndex = 1; if (openFile.ShowDialog() != DialogResult.OK) { return; } Form form = new Form(); BufferedWaveStream waveStream = null; VideoFormPanel panel = new VideoFormPanel(); panel.Dock = DockStyle.Fill; form.Controls.Add(panel); var media = new MediaReader(openFile.FileName); var decoder = media.Decoders.OfType<VideoDecoder>().First(); var audioDecoder = media.Decoders.OfType<AudioDecoder>().First(); decoder.OutFormat = new VideoFormat(decoder.InFormat.Width, decoder.InFormat.Height, AVPixelFormat.Bgr24, 4); VideoFrame frame = new VideoFrame(); AudioFrame audio = new AudioFrame(); AudioFrame convertedAudio = new AudioFrame(); waveStream = new BufferedWaveStream(new Coolest.WaveFormat(audioDecoder.InFormat.SampleRate, audioDecoder.InFormat.BitsPerSample, audioDecoder.InFormat.Channels)); Coolest.Windows.WasapiOut waveOut = new Coolest.Windows.WasapiOut(ShareMode.Shared, true, Role.Multimedia, 640); waveOut.Initialize(waveStream); AudioResampler resampler = new AudioResampler(AudioFormatFromWaveFormat(waveStream.Format, audioDecoder.InFormat.IsPlanarFormat), AudioFormatFromWaveFormat(waveOut.OutFormat)); waveOut.Resample += (o, format) => { audioDecoder.OutFormat = new AudioFormat(format.OutFormat.SampleRate, format.OutFormat.Channels, format.OutFormat.ValidBitsPerSample); }; Thread thread = new Thread(new ThreadStart(() => { while (true) { bool hasVideo = false; bool hasAudio = false; //利用ReadPacket取得Packet Packet packet = media.ReadPacket(); if (packet == null) break; if (packet.StreamIndex == decoder.StreamIndex) { //影像串流 hasVideo = decoder.Decode(packet, frame); if (hasVideo) { Bitmap image = new Bitmap(frame.Format.Width, frame.Format.Height, frame.Format.Strides[0], PixelFormat.Format24bppRgb, frame.Scan0); DateTime dt = DateTime.Now; panel.UpdateBitmap(image); double elapsed = DateTime.Now.Subtract(dt).TotalMilliseconds; double sleepTime = (1000.0 / media.FramesPerSecond) - elapsed; if (sleepTime > 0) Thread.Sleep((int)sleepTime); } } else if (packet.StreamIndex == audioDecoder.StreamIndex) { //聲音串流 // TODO push to another thread hasAudio = audioDecoder.Decode(packet, audio); if (hasAudio) { //利用resampler轉換成waveOut期待收到的格式 resampler.Convert(audio, convertedAudio); int bytes = convertedAudio.LineDataBytes; unsafe { for (int i = 0; i < convertedAudio.Data.Length; ++i) { if (convertedAudio.Data[i].Equals(IntPtr.Zero)) break; byte* ptr = (byte*)convertedAudio.Data[i].ToPointer(); int length = convertedAudio.LineDataBytes; waveStream.AddSample(ptr, 0, length); } } } } if (form.IsDisposed) break; } })); form.Disposed += (o, e) => { thread.Abort(); }; thread.IsBackground = true; waveOut.Play(); thread.Start(); Application.Run(form); } } }
SaarFFMPEG: VideoTest as a player

Be the first to comment

You can use [html][/html], [css][/css], [php][/php] and more to embed the code. Urls are automatically hyperlinked. Line breaks and paragraphs are automatically generated.