-
-
Save lovmoon3k/3ea5814f101d30deea0ba461022decc8 to your computer and use it in GitHub Desktop.
This file contains hidden or 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