lpms: Netint hardware support (#318)

* Netint hardware support

* make netcat flags depend on OS

* Self-review, cleanup, temporary disable some failing tests

* Uncomment signature test

* Support HEVC for Netint, refactor decoder selection

* Uncomment signature test, cleanup

* Set profile to high for h264 in transcoding script

* Pass profile in xcoderParams for Netint

* enable detectionFreq test for merging
This commit is contained in:
Ivan Poleshchuk
2022-04-19 17:38:07 +04:00
committed by GitHub
parent c1b78a46a6
commit 8b5dca93fe
12 changed files with 138 additions and 69 deletions
+7 -2
View File
@@ -26,12 +26,15 @@ func main() {
var err error
args := append([]string{os.Args[0]}, flag.Args()...)
if len(args) <= 3 {
panic("Usage: [-hevc] [-from dur] [-to dur] <input file> <output renditions, comma separated> <sw/nv>")
panic("Usage: [-hevc] [-from dur] [-to dur] <input file> <output renditions, comma separated> <sw/nv/nt>")
}
str2accel := func(inp string) (ffmpeg.Acceleration, string) {
if inp == "nv" {
return ffmpeg.Nvidia, "nv"
}
if inp == "nt" {
return ffmpeg.Netint, "nt"
}
return ffmpeg.Software, "sw"
}
str2profs := func(inp string) []ffmpeg.VideoProfile {
@@ -44,6 +47,8 @@ func main() {
}
if *hevc {
p.Encoder = ffmpeg.H265
} else {
p.Profile = ffmpeg.ProfileH264High
}
profs = append(profs, p)
}
@@ -72,7 +77,7 @@ func main() {
options := profs2opts(profiles)
var dev string
if accel == ffmpeg.Nvidia {
if accel != ffmpeg.Software {
if len(args) <= 4 {
panic("Expected device number")
}
Regular → Executable
+40 -27
View File
@@ -165,16 +165,6 @@ static enum AVPixelFormat get_hw_pixfmt(AVCodecContext *vc, const enum AVPixelFo
ret = av_hwframe_ctx_init(vc->hw_frames_ctx);
if (AVERROR(ENOSYS) == ret) ret = lpms_ERR_INPUT_PIXFMT; // most likely
if (ret < 0) LPMS_ERR(pixfmt_cleanup, "Unable to initialize a hardware frame pool");
/*
fprintf(stderr, "selected format: hw %s sw %s\n",
av_get_pix_fmt_name(frames->format), av_get_pix_fmt_name(frames->sw_format));
const enum AVPixelFormat *p;
for (p = pix_fmts; *p != -1; p++) {
fprintf(stderr,"possible format: %s\n", av_get_pix_fmt_name(*p));
}
*/
return frames->format;
pixfmt_cleanup:
@@ -211,19 +201,35 @@ open_audio_err:
return ret;
}
char* get_hw_decoder(int ff_codec_id)
char* get_hw_decoder(int ff_codec_id, int hw_type)
{
switch (ff_codec_id) {
case AV_CODEC_ID_H264:
return "h264_cuvid";
case AV_CODEC_ID_HEVC:
return "hevc_cuvid";
case AV_CODEC_ID_VP8:
return "vp8_cuvid";
case AV_CODEC_ID_VP9:
return "vp9_cuvid";
default:
return "";
switch (hw_type) {
case AV_HWDEVICE_TYPE_CUDA:
switch (ff_codec_id) {
case AV_CODEC_ID_H264:
return "h264_cuvid";
case AV_CODEC_ID_HEVC:
return "hevc_cuvid";
case AV_CODEC_ID_VP8:
return "vp8_cuvid";
case AV_CODEC_ID_VP9:
return "vp9_cuvid";
default:
return "";
}
case AV_HWDEVICE_TYPE_MEDIACODEC:
switch (ff_codec_id) {
case AV_CODEC_ID_H264:
return "h264_ni_dec";
case AV_CODEC_ID_HEVC:
return "h265_ni_dec";
case AV_CODEC_ID_VP8:
return "";
case AV_CODEC_ID_VP9:
return "";
default:
return "";
}
}
}
@@ -231,6 +237,7 @@ int open_video_decoder(input_params *params, struct input_ctx *ctx)
{
int ret = 0;
AVCodec *codec = NULL;
AVDictionary **opts = NULL;
AVFormatContext *ic = ctx->ic;
// open video decoder
ctx->vi = av_find_best_stream(ic, AVMEDIA_TYPE_VIDEO, -1, -1, &codec, 0);
@@ -238,8 +245,8 @@ int open_video_decoder(input_params *params, struct input_ctx *ctx)
else if (ctx->vi < 0) {
LPMS_WARN("No video stream found in input");
} else {
if (AV_HWDEVICE_TYPE_CUDA == params->hw_type) {
char* decoder_name = get_hw_decoder(codec->id);
if (params->hw_type > AV_HWDEVICE_TYPE_NONE) {
char* decoder_name = get_hw_decoder(codec->id, params->hw_type);
if (!*decoder_name) {
ret = lpms_ERR_INPUT_CODEC;
LPMS_ERR(open_decoder_err, "Input codec does not support hardware acceleration");
@@ -253,6 +260,11 @@ int open_video_decoder(input_params *params, struct input_ctx *ctx)
ret = lpms_ERR_INPUT_PIXFMT;
LPMS_ERR(open_decoder_err, "Non 4:2:0 pixel format detected in input");
}
} else if (params->video.name && strlen(params->video.name) != 0) {
// Try to find user specified decoder by name
AVCodec *c = avcodec_find_decoder_by_name(params->video.name);
if (c) codec = c;
if (params->video.opts) opts = &params->video.opts;
}
AVCodecContext *vc = avcodec_alloc_context3(codec);
if (!vc) LPMS_ERR(open_decoder_err, "Unable to alloc video codec");
@@ -261,16 +273,17 @@ int open_video_decoder(input_params *params, struct input_ctx *ctx)
if (ret < 0) LPMS_ERR(open_decoder_err, "Unable to assign video params");
vc->opaque = (void*)ctx;
// XXX Could this break if the original device falls out of scope in golang?
if (params->hw_type != AV_HWDEVICE_TYPE_NONE) {
if (params->hw_type == AV_HWDEVICE_TYPE_CUDA) {
// First set the hw device then set the hw frame
ret = av_hwdevice_ctx_create(&ctx->hw_device_ctx, params->hw_type, params->device, NULL, 0);
if (ret < 0) LPMS_ERR(open_decoder_err, "Unable to open hardware context for decoding")
ctx->hw_type = params->hw_type;
vc->hw_device_ctx = av_buffer_ref(ctx->hw_device_ctx);
vc->get_format = get_hw_pixfmt;
}
ctx->hw_type = params->hw_type;
vc->pkt_timebase = ic->streams[ctx->vi]->time_base;
ret = avcodec_open2(vc, codec, NULL);
av_opt_set(vc->priv_data, "xcoder-params", ctx->xcoderParams, 0);
ret = avcodec_open2(vc, codec, opts);
if (ret < 0) LPMS_ERR(open_decoder_err, "Unable to open video decoder");
}
Regular → Executable
+3 -1
View File
@@ -3,6 +3,7 @@
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/opt.h>
#include "transcoder.h"
struct input_ctx {
@@ -16,6 +17,7 @@ struct input_ctx {
AVBufferRef *hw_device_ctx;
enum AVHWDeviceType hw_type;
char *device;
char *xcoderParams;
// Decoder flush
AVPacket *first_pkt;
@@ -58,7 +60,7 @@ enum AVPixelFormat hw2pixfmt(AVCodecContext *ctx);
int open_input(input_params *params, struct input_ctx *ctx);
int open_video_decoder(input_params *params, struct input_ctx *ctx);
int open_audio_decoder(input_params *params, struct input_ctx *ctx);
char* get_hw_decoder(int ff_codec_id);
char* get_hw_decoder(int ff_codec_id, int hw_type);
void free_input(struct input_ctx *inctx);
// Utility functions
Regular → Executable
+10 -4
View File
@@ -155,7 +155,7 @@ void close_output(struct output_ctx *octx)
avformat_free_context(octx->oc);
octx->oc = NULL;
}
if (octx->vc && AV_HWDEVICE_TYPE_NONE == octx->hw_type) avcodec_free_context(&octx->vc);
if (octx->vc && octx->hw_type == AV_HWDEVICE_TYPE_NONE) avcodec_free_context(&octx->vc);
if (octx->ac) avcodec_free_context(&octx->ac);
octx->af.flushed = octx->vf.flushed = 0;
octx->af.flushing = octx->vf.flushing = 0;
@@ -252,6 +252,9 @@ int open_output(struct output_ctx *octx, struct input_ctx *ictx)
}
vc->pix_fmt = av_buffersink_get_format(octx->vf.sink_ctx); // XXX select based on encoder + input support
if (fmt->flags & AVFMT_GLOBALHEADER) vc->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
if(strcmp(octx->xcoderParams,"")!=0){
av_opt_set(vc->priv_data, "xcoder-params", octx->xcoderParams, 0);
}
ret = avcodec_open2(vc, codec, &octx->video->opts);
if (ret < 0) LPMS_ERR(open_output_err, "Error opening video encoder");
octx->hw_type = ictx->hw_type;
@@ -345,7 +348,8 @@ static int encode(AVCodecContext* encoder, AVFrame *frame, struct output_ctx* oc
// We don't want to send NULL frames for HW encoding
// because that closes the encoder: not something we want
if (AV_HWDEVICE_TYPE_NONE == octx->hw_type || AVMEDIA_TYPE_AUDIO == ost->codecpar->codec_type || frame) {
if (AV_HWDEVICE_TYPE_NONE == octx->hw_type || AV_HWDEVICE_TYPE_MEDIACODEC == octx->hw_type ||
AVMEDIA_TYPE_AUDIO == ost->codecpar->codec_type || frame) {
ret = avcodec_send_frame(encoder, frame);
if (AVERROR_EOF == ret) ; // continue ; drain encoder
else if (ret < 0) LPMS_ERR(encode_cleanup, "Error sending frame to encoder");
@@ -527,6 +531,7 @@ int process_out(struct input_ctx *ictx, struct output_ctx *octx, AVCodecContext
frame->pict_type = AV_PICTURE_TYPE_I;
octx->next_kf_pts = frame->pts + octx->gop_pts_len;
}
if(octx->is_dnn_profile) {
ret = getmetadatainf(frame, octx);
if(ret == -1 && frame == NULL) {
@@ -544,8 +549,9 @@ skip:
av_frame_unref(frame);
// For HW we keep the encoder open so will only get EAGAIN.
// Return EOF in place of EAGAIN for to terminate the flush
if (frame == NULL && AV_HWDEVICE_TYPE_NONE != octx->hw_type &&
AVERROR(EAGAIN) == ret && !inf) return AVERROR_EOF;
if (frame == NULL && octx->hw_type > AV_HWDEVICE_TYPE_NONE &&
AV_HWDEVICE_TYPE_MEDIACODEC != octx->hw_type &&
AVERROR(EAGAIN) == ret && !inf) return AVERROR_EOF;
if (frame == NULL) return ret;
}
+17 -17
View File
@@ -11,7 +11,7 @@ struct match_info {
int height;
uint64_t bit_rate;
int packetcount; //video total packet count
uint64_t timestamp; //XOR sum of avpacket pts
uint64_t timestamp; //XOR sum of avpacket pts
int audiosum[4]; //XOR sum of audio data's md5(16 bytes)
};
@@ -183,10 +183,10 @@ close_format_context:
return ret;
}
// compare two signature files whether those matches or not.
// @param signpath1 full path of the first signature file.
// @param signpath2 full path of the second signature file.
// @return <0: error 0: no matchiing 1: partial matching 2: whole matching.
//// compare two signature files whether those matches or not.
//// @param signpath1 full path of the first signature file.
//// @param signpath2 full path of the second signature file.
//// @return <0: error 0: no matchiing 1: partial matching 2: whole matching.
int lpms_compare_sign_bypath(char *signpath1, char *signpath2)
{
@@ -268,7 +268,7 @@ static int read_packet(void *opaque, uint8_t *buf, int buf_size)
return buf_size;
}
static int get_matchinfo(void *buffer, int len, struct match_info* info)
static int get_matchinfo(void *buffer, int len, struct match_info* info)
{
int ret = 0;
AVFormatContext* ifmt_ctx = NULL;
@@ -292,7 +292,7 @@ static int get_matchinfo(void *buffer, int len, struct match_info* info)
LPMS_ERR(clean, "Error allocating buffer");
}
avio_in = avio_alloc_context(avio_ctx_buffer, avio_ctx_buffer_size, 0, &bd, &read_packet, NULL, NULL);
avio_in = avio_alloc_context(avio_ctx_buffer, avio_ctx_buffer_size, 0, &bd, &read_packet, NULL, NULL);
if (!avio_ctx_buffer) {
ret = AVERROR(ENOMEM);
LPMS_ERR(clean, "Error allocating context");
@@ -304,15 +304,15 @@ static int get_matchinfo(void *buffer, int len, struct match_info* info)
}
ifmt_ctx->pb = avio_in;
ifmt_ctx->flags = AVFMT_FLAG_CUSTOM_IO;
if ((ret = avformat_open_input(&ifmt_ctx, "", NULL, NULL)) < 0) {
LPMS_ERR(clean, "Cannot open input video file\n");
}
if ((ret = avformat_find_stream_info(ifmt_ctx, NULL)) < 0) {
LPMS_ERR(clean, "Cannot find stream information\n");
}
for (int i = 0; i < ifmt_ctx->nb_streams; i++) {
AVStream *stream;
stream = ifmt_ctx->streams[i];
@@ -323,10 +323,10 @@ static int get_matchinfo(void *buffer, int len, struct match_info* info)
info->bit_rate = in_codecpar->bit_rate;
}
else if (in_codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
audioid = i;
audioid = i;
}
}
packet = av_packet_alloc();
packet = av_packet_alloc();
if (!packet) LPMS_ERR(clean, "Error allocating packet");
while (1) {
ret = av_read_frame(ifmt_ctx, packet);
@@ -334,7 +334,7 @@ static int get_matchinfo(void *buffer, int len, struct match_info* info)
ret = 0;
break;
}
else if (ret < 0) {
else if (ret < 0) {
LPMS_ERR(clean, "Unable to read input");
}
info->packetcount++;
@@ -345,8 +345,8 @@ static int get_matchinfo(void *buffer, int len, struct match_info* info)
}
av_packet_unref(packet);
}
clean:
clean:
if(packet)
av_packet_free(&packet);
/* note: the internal buffer could have changed, and be != avio_ctx_buffer */
@@ -365,7 +365,7 @@ clean:
// @return <0: error =0: matching 1: no matching
int lpms_compare_video_bybuffer(void *buffer1, int len1, void *buffer2, int len2)
{
int ret = 0;
int ret = 0;
struct match_info info1, info2;
ret = get_matchinfo(buffer1,len1,&info1);
@@ -374,7 +374,7 @@ int lpms_compare_video_bybuffer(void *buffer1, int len1, void *buffer2, int len2
ret = get_matchinfo(buffer2,len2,&info2);
if(ret < 0) return ret;
//compare two matching information
if (info1.width != info2.width || info1.height != info2.height ||
if (info1.width != info2.width || info1.height != info2.height ||
info1.bit_rate != info2.bit_rate || info1.packetcount != info2.packetcount ||
info1.timestamp != info2.timestamp || memcmp(info1.audiosum, info2.audiosum, 16)) {
ret = 1;
Regular → Executable
+39 -6
View File
@@ -48,8 +48,16 @@ const (
Software Acceleration = iota
Nvidia
Amd
Netint
)
var AccelerationNameLookup = map[Acceleration]string{
Software: "SW",
Nvidia: "Nvidia",
Amd: "Amd",
Netint: "Netint",
}
var FfEncoderLookup = map[Acceleration]map[VideoCodec]string{
Software: {
H264: "libx264",
@@ -61,6 +69,10 @@ var FfEncoderLookup = map[Acceleration]map[VideoCodec]string{
H264: "h264_nvenc",
H265: "hevc_nvenc",
},
Netint: {
H264: "h264_ni_enc",
H265: "h265_ni_enc",
},
}
type ComponentOptions struct {
@@ -440,6 +452,14 @@ func configEncoder(inOpts *TranscodeOptionsIn, outOpts TranscodeOptions, inDev,
}
return encoder, "scale_cuda", nil
}
case Netint:
switch outOpts.Accel {
case Software, Nvidia:
return "", "", ErrTranscoderDev // XXX don't allow mix-match between NETINT and sw/nv
case Netint:
// Use software scale filter
return encoder, "scale", nil
}
}
return "", "", ErrTranscoderHw
}
@@ -449,7 +469,8 @@ func accelDeviceType(accel Acceleration) (C.enum_AVHWDeviceType, error) {
return C.AV_HWDEVICE_TYPE_NONE, nil
case Nvidia:
return C.AV_HWDEVICE_TYPE_CUDA, nil
case Netint:
return C.AV_HWDEVICE_TYPE_MEDIACODEC, nil
}
return C.AV_HWDEVICE_TYPE_NONE, ErrTranscoderHw
}
@@ -491,6 +512,8 @@ func (t *Transcoder) Transcode(input *TranscodeOptionsIn, ps []TranscodeOptions)
}
}
fname := C.CString(input.Fname)
xcoderParams := C.CString("")
defer C.free(unsafe.Pointer(xcoderParams))
defer C.free(unsafe.Pointer(fname))
if input.Transmuxing {
t.started = true
@@ -542,7 +565,7 @@ func (t *Transcoder) Transcode(input *TranscodeOptionsIn, ps []TranscodeOptions)
// preserve aspect ratio along the larger dimension when rescaling
var filters string
filters = fmt.Sprintf("%s='w=if(gte(iw,ih),%d,-2):h=if(lt(iw,ih),%d,-2)'", scale_filter, w, h)
if input.Accel != Software && p.Accel == Software {
if input.Accel == Nvidia && p.Accel == Software {
// needed for hw dec -> hw rescale -> sw enc
filters = filters + ",hwdownload,format=nv12"
}
@@ -600,6 +623,10 @@ func (t *Transcoder) Transcode(input *TranscodeOptionsIn, ps []TranscodeOptions)
defer C.free(unsafe.Pointer(muxOpts.name))
}
// Set video encoder options
// TODO understand how h264 profiles and GOP setting works for
// NETINT encoder, and make sure we change relevant things here
// Any other options for the encoder can also be added here
xcoderOutParamsStr := ""
if len(p.VideoEncoder.Name) <= 0 && len(p.VideoEncoder.Opts) <= 0 {
p.VideoEncoder.Opts = map[string]string{
"forced-idr": "1",
@@ -609,8 +636,12 @@ func (t *Transcoder) Transcode(input *TranscodeOptionsIn, ps []TranscodeOptions)
p.VideoEncoder.Opts["profile"] = ProfileParameters[p.Profile.Profile]
p.VideoEncoder.Opts["bf"] = "0"
case ProfileH264Main, ProfileH264High:
p.VideoEncoder.Opts["profile"] = ProfileParameters[p.Profile.Profile]
p.VideoEncoder.Opts["bf"] = "3"
if p.Accel != Netint {
p.VideoEncoder.Opts["profile"] = ProfileParameters[p.Profile.Profile]
p.VideoEncoder.Opts["bf"] = "3"
} else {
xcoderOutParamsStr = "profile=high"
}
case ProfileNone:
if p.Accel == Nvidia {
p.VideoEncoder.Opts["bf"] = "0"
@@ -630,6 +661,8 @@ func (t *Transcoder) Transcode(input *TranscodeOptionsIn, ps []TranscodeOptions)
}
}
}
xcoderOutParams := C.CString(xcoderOutParamsStr)
defer C.free(unsafe.Pointer(xcoderOutParams))
gopMs := 0
if param.GOP != 0 {
if param.GOP <= GOPInvalid {
@@ -674,7 +707,7 @@ func (t *Transcoder) Transcode(input *TranscodeOptionsIn, ps []TranscodeOptions)
w: C.int(w), h: C.int(h), bitrate: C.int(bitrate),
gop_time: C.int(gopMs), from: C.int(fromMs), to: C.int(toMs),
muxer: muxOpts, audio: audioOpts, video: vidOpts,
vfilters: vfilt, sfilters: nil, is_dnn: isDNN}
vfilters: vfilt, sfilters: nil, is_dnn: isDNN, xcoderParams: xcoderOutParams}
if p.CalcSign {
//signfilter string
escapedOname := ffmpegStrEscape(p.Oname)
@@ -707,7 +740,7 @@ func (t *Transcoder) Transcode(input *TranscodeOptionsIn, ps []TranscodeOptions)
device = C.CString(input.Device)
defer C.free(unsafe.Pointer(device))
}
inp := &C.input_params{fname: fname, hw_type: hw_type, device: device,
inp := &C.input_params{fname: fname, hw_type: hw_type, device: device, xcoderParams: xcoderParams,
handle: t.handle}
if input.Transmuxing {
inp.transmuxe = 1
+1 -1
View File
@@ -71,7 +71,7 @@ struct output_ctx {
int is_dnn_profile; //if not dnn profile: 0
output_results *res; // data to return for this output
char *xcoderParams;
};
int init_video_filters(struct input_ctx *ictx, struct output_ctx *octx);
+2 -1
View File
@@ -8,7 +8,8 @@ import (
"testing"
)
func TestNvidia_Transcoding(t *testing.T) {
func
TestNvidia_Transcoding(t *testing.T) {
codecsComboTest(t, supportedCodecsCombinations([]Acceleration{Nvidia}))
}
+1 -2
View File
@@ -2,14 +2,13 @@ package ffmpeg
import (
"fmt"
"github.com/stretchr/testify/assert"
"io/ioutil"
"math/rand"
"os"
"path/filepath"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func Test_SignDataCreate(t *testing.T) {
Regular → Executable
+5 -3
View File
@@ -120,6 +120,7 @@ int transcode(struct transcode_thread *h,
{
int ret = 0, i = 0;
struct input_ctx *ictx = &h->ictx;
ictx->xcoderParams = inp->xcoderParams;
int reopen_decoders = !ictx->transmuxing;
struct output_ctx *outputs = h->outputs;
int nb_outputs = h->nb_outputs;
@@ -161,7 +162,7 @@ int transcode(struct transcode_thread *h,
if (reopen_decoders) {
// XXX check to see if we can also reuse decoder for sw decoding
if (AV_HWDEVICE_TYPE_CUDA != ictx->hw_type) {
if (ictx->hw_type == AV_HWDEVICE_TYPE_NONE) {
ret = open_video_decoder(inp, ictx);
if (ret < 0) LPMS_ERR(transcode_cleanup, "Unable to reopen video decoder");
}
@@ -180,6 +181,7 @@ int transcode(struct transcode_thread *h,
octx->video = &params[i].video;
octx->vfilters = params[i].vfilters;
octx->sfilters = params[i].sfilters;
octx->xcoderParams = params[i].xcoderParams;
if (params[i].is_dnn && h->dnn_filtergraph != NULL) {
octx->is_dnn_profile = params[i].is_dnn;
octx->dnn_filtergraph = &h->dnn_filtergraph;
@@ -390,7 +392,7 @@ whileloop_end:
}
else if(outputs[i].is_dnn_profile && outputs[i].res->frames > 0) {
for (int j = 0; j < MAX_CLASSIFY_SIZE; j++) {
outputs[i].res->probs[j] = outputs[i].res->probs[j] / outputs[i].res->frames;
outputs[i].res->probs[j] = outputs[i].res->probs[j] / outputs[i].res->frames;
}
}
}
@@ -416,7 +418,7 @@ transcode_cleanup:
if (ipkt) av_packet_free(&ipkt); // needed for early exits
if (ictx->first_pkt) av_packet_free(&ictx->first_pkt);
if (ictx->ac) avcodec_free_context(&ictx->ac);
if (ictx->vc && AV_HWDEVICE_TYPE_NONE == ictx->hw_type) avcodec_free_context(&ictx->vc);
if (ictx->vc && (AV_HWDEVICE_TYPE_NONE == ictx->hw_type)) avcodec_free_context(&ictx->vc);
for (i = 0; i < nb_outputs; i++) {
//send EOF signal to signature filter
if(outputs[i].sfilters != NULL && outputs[i].sf.src_ctx != NULL) {
Regular → Executable
+5 -2
View File
@@ -32,11 +32,10 @@ typedef struct {
int w, h, bitrate, gop_time, from, to;
AVRational fps;
int is_dnn;
char *xcoderParams;
component_opts muxer;
component_opts audio;
component_opts video;
} output_params;
typedef struct {
@@ -51,6 +50,10 @@ typedef struct {
// Optional hardware acceleration
enum AVHWDeviceType hw_type;
char *device;
char *xcoderParams;
// Optional video decoder + opts
component_opts video;
int transmuxe;
} input_params;
+8 -3
View File
@@ -9,6 +9,7 @@ import (
"os"
"os/exec"
"path"
"runtime"
"testing"
"time"
@@ -437,15 +438,19 @@ func (s *ServerDisconnectStream) ReadRTMPFromStream(ctx context.Context, dst av.
func TestServerDisconnect(t *testing.T) {
ffmpeg.InitFFmpeg()
port := "1938" // because we can't yet close the listener on 1935?
port := 1938 // because we can't yet close the listener on 1935?
strm := &ServerDisconnectStream{}
strmUrl := fmt.Sprintf("rtmp://localhost:%v/stream/%v", port, strm.GetStreamID())
opt := SegmenterOptions{SegLength: time.Second * 4}
vs := NewFFMpegVideoSegmenter("tmp", strm.GetStreamID(), strmUrl, opt)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
cmd := "dd if=/dev/urandom count=1 ibs=2000 | nc -l " + port
nopt := "N"
if runtime.GOOS == `darwin` {
// N is invalid on MacOS
nopt = ""
}
cmd := fmt.Sprintf("dd if=/dev/urandom count=1 ibs=2000 | nc -%sl %d", nopt, port)
go exec.CommandContext(ctx, "bash", "-c", cmd).Output()
err := RunRTMPToHLS(vs, ctx)