Skip to content

Instantly share code, notes, and snippets.

@tqk2811
Last active April 10, 2024 10:53
Show Gist options
  • Save tqk2811/94d31eb559f9a7fab861bce9507be592 to your computer and use it in GitHub Desktop.
Save tqk2811/94d31eb559f9a7fab861bce9507be592 to your computer and use it in GitHub Desktop.
internal static class Extensions
{
static unsafe string av_strerror(int error)
{
var bufferSize = 1024;
var buffer = stackalloc byte[bufferSize];
ffmpeg.av_strerror(error, buffer, (ulong)bufferSize);
var message = Marshal.PtrToStringAnsi((IntPtr)buffer);
return message;
}
internal static int ThrowExceptionIfError(this int error)
{
if (error < 0) throw new ApplicationException(av_strerror(error));
return error;
}
}
using System;
using FFmpeg;
using FFmpeg.AutoGen;
using static FFmpeg.AutoGen.ffmpeg;
namespace StreamVideo.Ffmpeg
{
public unsafe class LiveStream : IDisposable
{
public static string RootPath
{
set { ffmpeg.RootPath = value; }
}
const int SEEK_SET = 0;
const int SEEK_CUR = 1;
const int SEEK_END = 2;
readonly string videoPath;
readonly string url;
AVFormatContext* pInputFormatContext;
AVFormatContext* pOutputFormatContext;
public bool IsRunning { get; private set; } = true;
public LiveStream(string videoPath, string url)
{
this.videoPath = videoPath;
this.url = url;
}
//https://github.com/juniorxsound/libav-RTMP-Streaming/blob/master/src/streamer.cpp
//https://stackoverflow.com/questions/45526098/repeating-ffmpeg-stream-libavcodec-libavformat
public void Start()
{
int err = 0;
pInputFormatContext = avformat_alloc_context();
fixed (AVFormatContext** fix_pInputFormatContext = &pInputFormatContext)
err = avformat_open_input(fix_pInputFormatContext, videoPath, null, null).ThrowExceptionIfError();
err = avformat_find_stream_info(pInputFormatContext, null).ThrowExceptionIfError();
int videoIndex = -1;
int audioIndex = -1;
for (int i = 0; i < pInputFormatContext->nb_streams; i++)
{
if (videoIndex == -1 && pInputFormatContext->streams[i]->codecpar->codec_type == AVMediaType.AVMEDIA_TYPE_VIDEO)
{
videoIndex = i;
}
if (audioIndex == -1 && pInputFormatContext->streams[i]->codecpar->codec_type == AVMediaType.AVMEDIA_TYPE_AUDIO)
{
audioIndex = i;
}
}
if (videoIndex == -1 || audioIndex == -1) throw new Exception("Không tìm thấy video và audio trong file");
AVStream* input_video_stream = pInputFormatContext->streams[videoIndex];
AVStream* input_audio_stream = pInputFormatContext->streams[audioIndex];
fixed (AVFormatContext** fix_pOutputFormatContext = &pOutputFormatContext)
err = avformat_alloc_output_context2(fix_pOutputFormatContext, null, "flv", url).ThrowExceptionIfError();
AVStream* out_video_stream = avformat_new_stream(pOutputFormatContext, input_video_stream->codec->codec);
if (out_video_stream == null) throw new Exception("avformat_new_stream for input_video_stream failed");
avcodec_copy_context(out_video_stream->codec, input_video_stream->codec).ThrowExceptionIfError();
AVStream* out_audio_stream = avformat_new_stream(pOutputFormatContext, input_audio_stream->codec->codec);
if (out_audio_stream == null) throw new Exception("avformat_new_stream for input_audio_stream failed");
avcodec_copy_context(out_audio_stream->codec, input_audio_stream->codec).ThrowExceptionIfError();
avio_open(&pOutputFormatContext->pb, url, AVIO_FLAG_WRITE).ThrowExceptionIfError();
avformat_write_header(pOutputFormatContext, null).ThrowExceptionIfError();
AVRational time_base_q;
time_base_q.num = 1;
time_base_q.den = AV_TIME_BASE;
AVPacket pkt;
long startTime = av_gettime();
long readInputTime = startTime;
while (IsRunning)
{
try
{
err = av_read_frame(pInputFormatContext, &pkt);
if (err == AVERROR_EOF)
{
avio_seek(pInputFormatContext->pb, 0, SEEK_SET);
av_seek_frame(pInputFormatContext, videoIndex, 0, AVSEEK_FLAG_BACKWARD).ThrowExceptionIfError();
av_seek_frame(pInputFormatContext, audioIndex, 0, AVSEEK_FLAG_BACKWARD).ThrowExceptionIfError();
readInputTime = av_gettime();//reset
continue;
}
else err.ThrowExceptionIfError();
//send stream
if (pkt.stream_index == videoIndex || pkt.stream_index == audioIndex)
{
//check pst/pdt -> delay
AVRational time_base = pkt.stream_index == videoIndex ?
pInputFormatContext->streams[videoIndex]->time_base :
pInputFormatContext->streams[audioIndex]->time_base;
long pts_time = av_rescale_q(pkt.dts, time_base, time_base_q);
long now_time = av_gettime() - readInputTime;
if (pts_time > now_time)
{
uint diff_us = (uint)(pts_time - now_time);
av_usleep(diff_us);
}
pkt.dts += (readInputTime - startTime) / 1000;
pkt.pts = pkt.dts;
av_interleaved_write_frame(pOutputFormatContext, &pkt).ThrowExceptionIfError();
}
}
finally
{
av_packet_unref(&pkt);
}
}
}
public void Stop()
{
IsRunning = false;
}
public void Dispose()
{
avio_close(pOutputFormatContext->pb);
fixed (AVFormatContext** fix_pInputFormatContext = &pInputFormatContext) avformat_close_input(fix_pInputFormatContext);
avformat_free_context(pOutputFormatContext);
avformat_free_context(pInputFormatContext);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment