Last active
April 10, 2024 10:53
-
-
Save tqk2811/94d31eb559f9a7fab861bce9507be592 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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