Skip to content

Commit

Permalink
ffmpeg: Add standalone tests around stream copy/drop.
Browse files Browse the repository at this point in the history
  • Loading branch information
j0sh committed Sep 12, 2019
1 parent 356458e commit 29985e9
Show file tree
Hide file tree
Showing 2 changed files with 205 additions and 43 deletions.
228 changes: 191 additions & 37 deletions ffmpeg/ffmpeg_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -695,6 +695,193 @@ func TestTranscoder_EncoderOpts(t *testing.T) {
run(cmd)
}

func TestTranscoder_StreamCopy(t *testing.T) {
run, dir := setupTest(t)
defer os.RemoveAll(dir)

// Set up inputs, truncate test file
cmd := `
set -eux
cd "$0"
cp "$1"/../transcoder/test.ts .
ffmpeg -i test.ts -c:a copy -c:v copy -t 1 test-short.ts
# sanity check some assumptions here for the following set of tests
ffprobe -count_frames -show_streams -select_streams v test-short.ts | grep nb_read_frames=60
`
run(cmd)

// Test normal stream-copy case
in := &TranscodeOptionsIn{Fname: dir + "/test-short.ts"}
out := []TranscodeOptions{
TranscodeOptions{
Oname: dir + "/audiocopy.ts",
Profile: P144p30fps16x9,
AudioEncoder: ComponentOptions{Name: "copy"},
},
TranscodeOptions{
Oname: dir + "/videocopy.ts",
VideoEncoder: ComponentOptions{Name: "copy", Opts: map[string]string{
"mpegts_flags": "resend_headers,initial_discontinuity",
}},
},
}
res, err := Transcode3(in, out)
if err != nil {
t.Error(err)
}
if res.Decoded.Frames != 60 || res.Encoded[0].Frames != 30 ||
res.Encoded[1].Frames != 0 {
t.Error("Unexpected frame counts from stream copy")
t.Error(res)
}

cmd = `
set -eux
cd "$0"
# extract video track only, compare md5sums
ffmpeg -i test-short.ts -an -c:v copy -f md5 test-video.md5
ffmpeg -i videocopy.ts -an -c:v copy -f md5 videocopy.md5
diff -u test-video.md5 videocopy.md5
# extract audio track only, compare md5sums
ffmpeg -i test-short.ts -vn -c:a copy -f md5 test-audio.md5
ffmpeg -i audiocopy.ts -vn -c:a copy -f md5 audiocopy.md5
diff -u test-audio.md5 audiocopy.md5
`
run(cmd)

// Test stream copy when no stream exists in file
cmd = `
set -eux
cd "$0"
ffmpeg -i test-short.ts -an -c:v copy videoonly.ts
ffmpeg -i test-short.ts -vn -c:a copy audioonly.ts
`
run(cmd)
in = &TranscodeOptionsIn{Fname: dir + "/videoonly.ts"}
out = []TranscodeOptions{
TranscodeOptions{
Oname: dir + "/novideo.ts",
VideoEncoder: ComponentOptions{Name: "copy"},
},
}
res, err = Transcode3(in, out)
if err != nil {
t.Error(err)
}
if res.Decoded.Frames != 0 || res.Encoded[0].Frames != 0 {
t.Error("Unexpected count of decoded/encoded frames")
}
in = &TranscodeOptionsIn{Fname: dir + "/audioonly.ts"}
out = []TranscodeOptions{
TranscodeOptions{
Oname: dir + "/noaudio.ts",
Profile: P144p30fps16x9,
AudioEncoder: ComponentOptions{Name: "copy"},
},
}
res, err = Transcode3(in, out)
if err != nil {
t.Error(err)
}
if res.Decoded.Frames != 0 || res.Encoded[0].Frames != 0 {
t.Error("Unexpected count of decoded frames")
}
}

func TestTranscoder_Drop(t *testing.T) {
run, dir := setupTest(t)
defer os.RemoveAll(dir)

cmd := `
set -eux
cd "$0"
cp "$1"/../transcoder/test.ts .
ffmpeg -i test.ts -c:a copy -c:v copy -t 1 test-short.ts
# sanity check some assumptions here for the following set of tests
ffprobe -count_frames -show_streams -select_streams v test-short.ts | grep nb_read_frames=60
`
run(cmd)

// Normal case : drop only video
in := &TranscodeOptionsIn{Fname: dir + "/test-short.ts"}
out := []TranscodeOptions{
TranscodeOptions{
Oname: dir + "/novideo.ts",
VideoEncoder: ComponentOptions{Name: "drop"},
},
}
res, err := Transcode3(in, out)
if err != nil {
t.Error(err)
}
if res.Decoded.Frames != 0 || res.Encoded[0].Frames != 0 {
t.Error("Unexpected count of decoded frames ", res.Decoded.Frames, res.Decoded.Pixels)
}

// Normal case: drop only audio
out = []TranscodeOptions{
TranscodeOptions{
Oname: dir + "/noaudio.ts",
AudioEncoder: ComponentOptions{Name: "drop"},
Profile: P144p30fps16x9,
},
}
res, err = Transcode3(in, out)
if err != nil {
t.Error(err)
}
if res.Decoded.Frames != 60 || res.Encoded[0].Frames != 30 {
t.Error("Unexpected count of decoded frames ", res.Decoded.Frames, res.Decoded.Pixels)
}

// Test error when trying to mux no streams
out = []TranscodeOptions{TranscodeOptions{
Oname: dir + "/none.mp4",
VideoEncoder: ComponentOptions{Name: "drop"},
AudioEncoder: ComponentOptions{Name: "drop"},
}}
_, err = Transcode3(in, out)
if err == nil || err.Error() != "Invalid argument" {
t.Error("Did not get expected error: ", err)
}

// Test error when missing profile in default video configuration
out = []TranscodeOptions{TranscodeOptions{
Oname: dir + "/profile.mp4",
AudioEncoder: ComponentOptions{Name: "drop"},
}}
_, err = Transcode3(in, out)
if err == nil || err != ErrTranscoderRes {
t.Error("Expected res err related to profile, but got ", err)
}

// Sanity check default transcode options with single-stream input
in.Fname = dir + "/noaudio.ts"
out = []TranscodeOptions{TranscodeOptions{Oname: dir + "/encoded-video.mp4", Profile: P144p30fps16x9}}
res, err = Transcode3(in, out)
if err != nil {
t.Error(err)
}
if res.Decoded.Frames != 30 || res.Encoded[0].Frames != 29 { // XXX 29 ?!?
t.Error("Unexpected encoded/decoded frame counts ", res.Decoded.Frames, res.Encoded[0].Frames)
}
in.Fname = dir + "/novideo.ts"
out = []TranscodeOptions{TranscodeOptions{Oname: dir + "/encoded-audio.mp4", Profile: P144p30fps16x9}}
res, err = Transcode3(in, out)
if err != nil {
t.Error(err)
}
if res.Decoded.Frames != 0 || res.Encoded[0].Frames != 0 {
t.Error("Unexpected encoded/decoded frame counts ")
}
}

func TestTranscoder_StreamCopyAndDrop(t *testing.T) {
run, dir := setupTest(t)
defer os.RemoveAll(dir)
Expand Down Expand Up @@ -729,10 +916,13 @@ func TestTranscoder_StreamCopyAndDrop(t *testing.T) {
VideoEncoder: ComponentOptions{Name: "copy"},
AudioEncoder: ComponentOptions{Name: "copy"},
}}
_, err := Transcode3(in, out)
res, err := Transcode3(in, out)
if err != nil {
t.Error(err)
}
if res.Decoded.Frames != 0 {
t.Error("Unexpected count for decoded frames ", res.Decoded.Frames)
}
cmd := `
set -eux
cd $0
Expand Down Expand Up @@ -831,28 +1021,6 @@ func TestTranscoder_StreamCopyAndDrop(t *testing.T) {
t.Error("Expected error converting audio from ts to flv but got ", err)
}

// Test error when trying to mux no streams
in.Fname = "../transcoder/test.ts"
out = []TranscodeOptions{TranscodeOptions{
Oname: dir + "/none.mp4",
VideoEncoder: ComponentOptions{Name: "drop"},
AudioEncoder: ComponentOptions{Name: "drop"},
}}
_, err = Transcode3(in, out)
if err == nil || err.Error() != "Invalid argument" {
t.Error("Did not get expected error: ", err)
}

// Test error when missing profile in default video configuration
out = []TranscodeOptions{TranscodeOptions{
Oname: dir + "/profile.mp4",
AudioEncoder: ComponentOptions{Name: "drop"},
}}
_, err = Transcode3(in, out)
if err == nil || err != ErrTranscoderRes {
t.Error("Expected res err related to profile, but got ", err)
}

// Encode one stream of a short sample while copying / dropping another
in.Fname = dir + "/test-short.ts"
out = []TranscodeOptions{TranscodeOptions{
Expand All @@ -868,18 +1036,4 @@ func TestTranscoder_StreamCopyAndDrop(t *testing.T) {
t.Error(err)

}

// Sanity check default transcode options with single-stream input
in.Fname = dir + "/encoded-video.mp4"
out = []TranscodeOptions{TranscodeOptions{Oname: dir + "/encoded-video2.mp4", Profile: P144p30fps16x9}}
_, err = Transcode3(in, out)
if err != nil {
t.Error(err)
}
in.Fname = dir + "/encoded-audio.mp4"
out = []TranscodeOptions{TranscodeOptions{Oname: dir + "/encoded-audio2.mp4", Profile: P144p30fps16x9}}
_, err = Transcode3(in, out)
if err != nil {
t.Error(err)
}
}
20 changes: 14 additions & 6 deletions ffmpeg/lpms_ffmpeg.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
// Not great to appropriate internal API like this...
const int lpms_ERR_INPUT_PIXFMT = FFERRTAG('I','N','P','X');
const int lpms_ERR_FILTERS = FFERRTAG('F','L','T','R');
const int lpms_ERR_PACKET_ONLY = FFERRTAG('P','K','O','N');

//
// Internal transcoder data structures
Expand Down Expand Up @@ -671,15 +672,16 @@ int process_in(struct input_ctx *ictx, AVFrame *frame, AVPacket *pkt)
if (ret < 0) dec_err("Error sending packet to decoder\n");
ret = avcodec_receive_frame(decoder, frame);
if (ret == AVERROR(EAGAIN)) {
av_packet_unref(pkt);
continue;
// Distinguish from EAGAIN that may occur with
// av_read_frame or avcodec_send_packet
ret = lpms_ERR_PACKET_ONLY;
break;
}
else if (ret < 0) dec_err("Error receiving frame from decoder\n");
break;
}

dec_cleanup:
if (ret < 0) av_packet_unref(pkt); // XXX necessary? or have caller do it?
return ret;

dec_flush:
Expand Down Expand Up @@ -877,16 +879,20 @@ int lpms_transcode(input_params *inp, output_params *params,
if (!dframe) main_err("transcoder: Unable to allocate frame\n");

while (1) {
int has_frame = 0;
AVStream *ist = NULL;
av_frame_unref(dframe);
ret = process_in(&ictx, dframe, &ipkt);
if (ret == AVERROR_EOF) break;
// Bail out on streams that appear to be broken
else if (lpms_ERR_PACKET_ONLY == ret) ; // keep going for stream copy
else if (ret < 0) main_err("transcoder: Could not decode; stopping\n");
ist = ictx.ic->streams[ipkt.stream_index];
has_frame = lpms_ERR_PACKET_ONLY != ret;

if (AVMEDIA_TYPE_VIDEO == ist->codecpar->codec_type) {
decoded_results->frames++;
// width / height will be zero for pure streamcopy (no decoding)
decoded_results->frames += dframe->width && dframe->height;
decoded_results->pixels += dframe->width * dframe->height;
}

Expand All @@ -895,6 +901,7 @@ int lpms_transcode(input_params *inp, output_params *params,
struct filter_ctx *filter = NULL;
AVStream *ost = NULL;
AVCodecContext *encoder = NULL;
ret = 0; // reset to avoid any carry-through

if (ist->index == ictx.vi) {
if (octx->dv) continue; // drop video stream for this output
Expand Down Expand Up @@ -923,9 +930,10 @@ int lpms_transcode(input_params *inp, output_params *params,
pkt = av_packet_clone(&ipkt);
if (!pkt) main_err("transcoder: Error allocating packet\n");
ret = mux(pkt, ist->time_base, octx, ost);

av_packet_unref(pkt);
} else ret = process_out(&ictx, octx, encoder, ost, filter, dframe);
} else if (has_frame) {
ret = process_out(&ictx, octx, encoder, ost, filter, dframe);
}
if (AVERROR(EAGAIN) == ret || AVERROR_EOF == ret) continue;
else if (ret < 0) main_err("transcoder: Error encoding\n");
}
Expand Down

0 comments on commit 29985e9

Please sign in to comment.