본 포스팅은
[C#/WPF] FFmpeg로 USB Camera(WebCam) Display 하기
시리즈의 보너스 판이다.
링크
[C#/WPF] FFmpeg로 USB Camera(WebCam) Display 하기 - 1
[C#/WPF] FFmpeg로 USB Camera(WebCam) Display 하기 - 2
[C#/WPF] FFmpeg로 USB Camera(WebCam) Display 하기 - 3
기본적인 환경설정과 대부분의 소스코드는 동일하다.
단 2개 소스 파일의 일부분만 변경하면 웹캠에서 RTP 혹은 RTSP 영상을 출력할 수 있다.
수정할 파일 목록이다.
VideoStreamDecoder.cs
MainWindow.xaml.cs
VideoStreamDecoder.cs
using FFmpeg.AutoGen;
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
namespace FFmpeg_usbCam.FFmpeg.Decoder
{
public sealed unsafe class VideoStreamDecoder : IDisposable
{
private readonly AVCodecContext* _pCodecContext;
private readonly AVFormatContext* _pFormatContext;
private readonly int _streamIndex;
private readonly AVFrame* _pFrame;
private readonly AVPacket* _pPacket;
public VideoStreamDecoder(string url)
{
_pFormatContext = ffmpeg.avformat_alloc_context();
var pFormatContext = _pFormatContext;
//미디어 파일 열기 url주소 또는 파일 이름 필요
ffmpeg.avformat_open_input(&pFormatContext, url, null, null).ThrowExceptionIfError();
////미디어 정보 가져옴, blocking 함수라서 network protocol으로 가져올 시, 블락될수도 있슴
ffmpeg.avformat_find_stream_info(_pFormatContext, null).ThrowExceptionIfError();
// find the first video stream
AVStream* pStream = null;
for (var i = 0; i < _pFormatContext->nb_streams; i++)
if (_pFormatContext->streams[i]->codec->codec_type == AVMediaType.AVMEDIA_TYPE_VIDEO)
{
pStream = _pFormatContext->streams[i];
break;
}
if (pStream == null) throw new InvalidOperationException("Could not found video stream.");
_streamIndex = pStream->index;
_pCodecContext = pStream->codec;
var codecId = _pCodecContext->codec_id;
var pCodec = ffmpeg.avcodec_find_decoder(codecId); //H264
if (pCodec == null) throw new InvalidOperationException("Unsupported codec.");
//open codec
ffmpeg.avcodec_open2(_pCodecContext, pCodec, null).ThrowExceptionIfError();
CodecName = ffmpeg.avcodec_get_name(codecId);
FrameSize = new System.Windows.Size(_pCodecContext->width, _pCodecContext->height);
PixelFormat = _pCodecContext->pix_fmt;
_pPacket = ffmpeg.av_packet_alloc();
_pFrame = ffmpeg.av_frame_alloc();
}
public string CodecName { get; }
public System.Windows.Size FrameSize { get; }
public AVPixelFormat PixelFormat { get; }
public void Dispose()
{
ffmpeg.av_frame_unref(_pFrame);
ffmpeg.av_free(_pFrame);
ffmpeg.av_packet_unref(_pPacket);
ffmpeg.av_free(_pPacket);
ffmpeg.avcodec_close(_pCodecContext);
var pFormatContext = _pFormatContext;
ffmpeg.avformat_close_input(&pFormatContext);
}
public bool TryDecodeNextFrame(out AVFrame frame)
{
ffmpeg.av_frame_unref(_pFrame);
int error;
do
{
try
{
do
{
error = ffmpeg.av_read_frame(_pFormatContext, _pPacket);
if (error == ffmpeg.AVERROR_EOF)
{
frame = *_pFrame;
return false;
}
error.ThrowExceptionIfError();
} while (_pPacket->stream_index != _streamIndex);
ffmpeg.avcodec_send_packet(_pCodecContext, _pPacket).ThrowExceptionIfError();
}
finally
{
ffmpeg.av_packet_unref(_pPacket);
}
error = ffmpeg.avcodec_receive_frame(_pCodecContext, _pFrame);
} while (error == ffmpeg.AVERROR(ffmpeg.EAGAIN));
error.ThrowExceptionIfError();
frame = *_pFrame;
return true;
}
public IReadOnlyDictionary<string, string> GetContextInfo()
{
AVDictionaryEntry* tag = null;
var result = new Dictionary<string, string>();
while ((tag = ffmpeg.av_dict_get(_pFormatContext->metadata, "", tag, ffmpeg.AV_DICT_IGNORE_SUFFIX)) != null)
{
var key = Marshal.PtrToStringAnsi((IntPtr)tag->key);
var value = Marshal.PtrToStringAnsi((IntPtr)tag->value);
result.Add(key, value);
}
return result;
}
}
}
MainWindow.xaml.cs
using System;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows;
using System.Windows.Media.Imaging;
using System.Windows.Threading;
using FFmpeg.AutoGen;
using FFmpeg_usbCam.FFmpeg.Decoder;
using System.Drawing;
using System.IO;
using FFmpeg_usbCam.FFmpeg;
namespace FFmpeg_usbCam
{
/// <summary>
/// MainWindow.xaml에 대한 상호 작용 논리
/// </summary>
public partial class MainWindow : Window
{
Thread thread;
ThreadStart ts;
Dispatcher dispatcher = Application.Current.Dispatcher;
private bool activeThread; //thread 활성화 유무
public MainWindow()
{
InitializeComponent();
//비디오 프레임 디코딩 thread 생성
ts = new ThreadStart(DecodeAllFramesToImages);
thread = new Thread(ts);
//FFmpeg dll 파일 참조 경로 설정
FFmpegBinariesHelper.RegisterFFmpegBinaries();
activeThread = true;
}
private void Play_Button_Click(object sender, RoutedEventArgs e)
{
//thread 시작
if (thread.ThreadState == ThreadState.Unstarted)
{
thread.Start();
}
}
private unsafe void DecodeAllFramesToImages()
{
//test rtsp 영상 주소
var url = "rtsp://184.72.239.149/vod/mp4:BigBuckBunny_115k.mov"; // be advised this file holds 1440 frames
using (var vsd = new VideoStreamDecoder(url))
{
var info = vsd.GetContextInfo();
info.ToList().ForEach(x => Console.WriteLine($"{x.Key} = {x.Value}"));
var sourceSize = vsd.FrameSize;
var sourcePixelFormat = vsd.PixelFormat;
var destinationSize = sourceSize;
var destinationPixelFormat = AVPixelFormat.AV_PIX_FMT_BGR24;
using (var vfc = new VideoFrameConverter(sourceSize, sourcePixelFormat, destinationSize, destinationPixelFormat))
{
var frameNumber = 0;
while (vsd.TryDecodeNextFrame(out var frame) && activeThread)
{
var convertedFrame = vfc.Convert(frame);
Bitmap bitmap;
bitmap = new Bitmap(convertedFrame.width, convertedFrame.height, convertedFrame.linesize[0], System.Drawing.Imaging.PixelFormat.Format24bppRgb, (IntPtr)convertedFrame.data[0]);
BitmapToImageSource(bitmap);
frameNumber++;
}
}
}
}
void BitmapToImageSource(Bitmap bitmap)
{
//UI thread에 접근하기 위해 dispatcher 사용
dispatcher.BeginInvoke((Action)(() =>
{
if (thread.IsAlive)
{
using (MemoryStream memory = new MemoryStream())
{
bitmap.Save(memory, System.Drawing.Imaging.ImageFormat.Bmp);
memory.Position = 0;
BitmapImage bitmapimage = new BitmapImage();
bitmapimage.BeginInit();
bitmapimage.CacheOption = BitmapCacheOption.OnLoad;
bitmapimage.StreamSource = memory;
bitmapimage.EndInit();
image.Source = bitmapimage; //image 컨트롤에 웹캠 이미지 표시
//memory.Dispose();
//GC.Collect();
}
}
}));
}
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
if (thread.IsAlive)
{
activeThread = false;
thread.Join();
}
}
}
}
수정 후 빌드하면 아래와 같이 테스트용 RTSP 영상이 출력된다.
특정 주소를 열고 싶다면 url만 수정하면 될 것이다.
'Programming > C#, WPF' 카테고리의 다른 글
[C#/WPF] Joystick 입력 데이터 디스플레이 [MVVM] (0) | 2019.02.28 |
---|---|
[C#/WPF] FFmpeg로 WebCam / RTP / RTSP 영상 Display & Recode (20) | 2019.02.27 |
[C#/WPF] FFmpeg로 USB Camera(WebCam) Display 하기 - 3 (9) | 2019.02.14 |
[C#/WPF] FFmpeg로 USB Camera(WebCam) Display 하기 - 2 (1) | 2019.02.14 |
[C#/WPF] FFmpeg로 USB Camera(WebCam) Display 하기 - 1 (1) | 2019.02.14 |