Skip to content

Commit b13487e

Browse files
authored
Playbacktest (#100)
* Added some tests of playback, using some code from ffmpeg to test seeking to a frame and extracting some individual frames. Signed-off-by: [email protected] <[email protected]> * Comparing different intraframe encoding options. Signed-off-by: [email protected] <[email protected]> * Adding PC tests. Signed-off-by: Sam Richards <[email protected]> * Adding lots of timing information. Signed-off-by: [email protected] <[email protected]> * Adding optimization for build. Signed-off-by: [email protected] <[email protected]> * Adding more timing breakdown. Signed-off-by: [email protected] <[email protected]> --------- Signed-off-by: [email protected] <[email protected]> Signed-off-by: Sam Richards <[email protected]> Co-authored-by: [email protected] <[email protected]>
1 parent 4ca7a2d commit b13487e

File tree

6 files changed

+754
-0
lines changed

6 files changed

+754
-0
lines changed

enctests/playback/CMakeLists.txt

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
cmake_minimum_required(VERSION 3.10)
2+
project(codec_test C)
3+
4+
if(NOT CMAKE_BUILD_TYPE)
5+
set(CMAKE_BUILD_TYPE Release)
6+
endif()
7+
8+
set(CMAKE_CXX_FLAGS_DEBUG "-g")
9+
set(CMAKE_CXX_FLAGS_RELEASE "-O3")
10+
11+
set(CMAKE_C_STANDARD 11)
12+
13+
if(CMAKE_COMPILER_IS_GNUCXX)
14+
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wpedantic")
15+
endif()
16+
17+
# Find FFmpeg packages
18+
find_package(PkgConfig REQUIRED)
19+
pkg_check_modules(FFMPEG REQUIRED IMPORTED_TARGET
20+
libavcodec
21+
libavformat
22+
libavutil
23+
libswscale
24+
)
25+
26+
# Add the executable
27+
add_executable(codec_test codec_test.c)
28+
29+
# Link against FFmpeg libraries
30+
target_link_libraries(codec_test PRIVATE PkgConfig::FFMPEG)
31+
32+
# Include FFmpeg headers
33+
target_include_directories(codec_test PRIVATE ${FFMPEG_INCLUDE_DIRS})
34+
35+
# If you're on macOS and using Homebrew, you might need to add this:
36+
if(APPLE)
37+
include_directories(/opt/homebrew/include)
38+
link_directories(/opt/homebrew/lib)
39+
endif()
40+
41+
# Add compiler flags
42+
target_compile_options(codec_test PRIVATE -Wall -Wextra -pedantic)

enctests/playback/README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
2+
* https://trac.ffmpeg.org/wiki/HWAccelIntro
3+
4+
TODO:
5+
* Test NVDEC
6+
* Rework to be able to run on multiple platforms and aggregate the results.
7+
* Add HAPQ.
8+
* 4K testing.
9+
10+

enctests/playback/codec_test.c

Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
#include <stdio.h>
2+
#include <stdlib.h>
3+
#include <string.h>
4+
#include <time.h>
5+
6+
#include <libavcodec/avcodec.h>
7+
#include <libavformat/avformat.h>
8+
#include <libavutil/timestamp.h>
9+
#include <libswscale/swscale.h>
10+
11+
#define ADDITIONAL_FRAMES 30
12+
13+
double get_time() {
14+
struct timespec ts;
15+
clock_gettime(CLOCK_MONOTONIC, &ts);
16+
return ts.tv_sec + ts.tv_nsec / 1e9;
17+
}
18+
19+
int decode_frames_with_library(const char* filename, const char* decoder_library, int64_t seek_frame) {
20+
AVFormatContext* format_ctx = NULL;
21+
AVCodecContext* codec_ctx = NULL;
22+
const AVCodec* codec = NULL;
23+
AVFrame* frame = NULL;
24+
AVPacket* packet = NULL;
25+
int video_stream_index = -1;
26+
int ret;
27+
28+
if (avformat_open_input(&format_ctx, filename, NULL, NULL) < 0) {
29+
fprintf(stderr, "Could not open file %s\n", filename);
30+
return -1;
31+
}
32+
33+
if (avformat_find_stream_info(format_ctx, NULL) < 0) {
34+
fprintf(stderr, "Could not find stream information\n");
35+
avformat_close_input(&format_ctx);
36+
return -1;
37+
}
38+
39+
for (int i = 0; i < format_ctx->nb_streams; i++) {
40+
if (format_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
41+
video_stream_index = i;
42+
break;
43+
}
44+
}
45+
46+
if (video_stream_index == -1) {
47+
fprintf(stderr, "Could not find video stream\n");
48+
avformat_close_input(&format_ctx);
49+
return -1;
50+
}
51+
52+
AVCodecParameters* codecpar = format_ctx->streams[video_stream_index]->codecpar;
53+
54+
codec = avcodec_find_decoder_by_name(decoder_library);
55+
if (!codec) {
56+
fprintf(stderr, "Decoder library %s not found\n", decoder_library);
57+
avformat_close_input(&format_ctx);
58+
return -1;
59+
}
60+
61+
if (codec->id != codecpar->codec_id) {
62+
fprintf(stderr, "Specified decoder library %s does not match the video codec\n", decoder_library);
63+
avformat_close_input(&format_ctx);
64+
return -1;
65+
}
66+
67+
codec_ctx = avcodec_alloc_context3(codec);
68+
if (!codec_ctx) {
69+
fprintf(stderr, "Could not allocate codec context\n");
70+
avformat_close_input(&format_ctx);
71+
return -1;
72+
}
73+
74+
75+
76+
77+
if (avcodec_parameters_to_context(codec_ctx, codecpar) < 0) {
78+
fprintf(stderr, "Could not copy codec parameters to context\n");
79+
avcodec_free_context(&codec_ctx);
80+
avformat_close_input(&format_ctx);
81+
return -1;
82+
}
83+
// Set threading options
84+
codec_ctx->thread_count = 0; // Let FFmpeg decide the number of threads
85+
codec_ctx->thread_type = FF_THREAD_FRAME | FF_THREAD_SLICE; // Use both frame and slice threading
86+
codec_ctx->flags2 |= AV_CODEC_FLAG2_FAST;
87+
codec_ctx->flags |= AV_CODEC_FLAG_LOW_DELAY;
88+
89+
if (avcodec_open2(codec_ctx, codec, NULL) < 0) {
90+
fprintf(stderr, "Could not open codec\n");
91+
avcodec_free_context(&codec_ctx);
92+
avformat_close_input(&format_ctx);
93+
return -1;
94+
}
95+
96+
frame = av_frame_alloc();
97+
packet = av_packet_alloc();
98+
if (!frame || !packet) {
99+
fprintf(stderr, "Could not allocate frame or packet\n");
100+
av_frame_free(&frame);
101+
av_packet_free(&packet);
102+
avcodec_free_context(&codec_ctx);
103+
avformat_close_input(&format_ctx);
104+
return -1;
105+
}
106+
107+
// Convert frame number to timestamp
108+
AVStream* video_stream = format_ctx->streams[video_stream_index];
109+
int64_t seek_timestamp = av_rescale_q(seek_frame,
110+
av_inv_q(video_stream->avg_frame_rate),
111+
video_stream->time_base);
112+
113+
114+
115+
int frames_decoded = 0;
116+
double total_decoding_time = 0.0;
117+
double first_frame_time = 0.0;
118+
clock_t start_time = clock();
119+
120+
if (av_seek_frame(format_ctx, video_stream_index, seek_timestamp, AVSEEK_FLAG_BACKWARD) < 0) {
121+
fprintf(stderr, "Error while seeking\n");
122+
av_frame_free(&frame);
123+
av_packet_free(&packet);
124+
avcodec_free_context(&codec_ctx);
125+
avformat_close_input(&format_ctx);
126+
return -1;
127+
}
128+
129+
avcodec_flush_buffers(codec_ctx);
130+
131+
clock_t end_time = clock();
132+
double frame_time = ((double) (end_time - start_time)) / CLOCKS_PER_SEC;
133+
fprintf(stderr, "Decoder seek: %s\nFirstFrame: %.6f\n", decoder_library, frame_time);
134+
start_time = clock();
135+
struct timespec start_time2;
136+
clock_gettime(CLOCK_MONOTONIC, &start_time2);
137+
138+
139+
while (av_read_frame(format_ctx, packet) >= 0 && frames_decoded <= ADDITIONAL_FRAMES) {
140+
clock_t end_time = clock();
141+
double frame_time = ((double) (end_time - start_time)) / CLOCKS_PER_SEC;
142+
fprintf(stderr, "avread_frame: %.6f\n", frame_time);
143+
if (packet->stream_index == video_stream_index) {
144+
145+
clock_t send_start_time = clock();
146+
ret = avcodec_send_packet(codec_ctx, packet);
147+
if (ret < 0) {
148+
fprintf(stderr, "Error sending packet for decoding\n");
149+
break;
150+
}
151+
152+
clock_t end_time = clock();
153+
double frame_time = ((double) (end_time - send_start_time)) / CLOCKS_PER_SEC;
154+
fprintf(stderr, "send_packet: %.6f\n", frame_time);
155+
156+
while (ret >= 0) {
157+
clock_t receive_time_start = clock();
158+
ret = avcodec_receive_frame(codec_ctx, frame);
159+
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
160+
//fprintf(stderr, "ERROR: %d\n", ret);
161+
break;
162+
} else if (ret < 0) {
163+
fprintf(stderr, "Error during decoding\n");
164+
goto end;
165+
}
166+
167+
clock_t end_time = clock();
168+
double frame_time = ((double) (end_time - start_time)) / CLOCKS_PER_SEC;
169+
double receive_frame_time = ((double) (end_time - receive_time_start)) / CLOCKS_PER_SEC;
170+
struct timespec end_time2;
171+
clock_gettime(CLOCK_MONOTONIC, &end_time2);
172+
double elapsed_seconds = (end_time2.tv_sec - start_time2.tv_sec) +
173+
(end_time2.tv_nsec - start_time2.tv_nsec) / 1e9;
174+
fprintf(stderr, "Decoder: %s Frame: %02d Duration:%.6f ReceiveDuration:%.6f Duration2:%.6f\n",
175+
decoder_library, frames_decoded, frame_time, receive_frame_time, elapsed_seconds);
176+
total_decoding_time += frame_time;
177+
178+
if (frames_decoded == 0) {
179+
first_frame_time = frame_time;
180+
printf("Decoder: %s\nFirstFrame: %.6f\n",
181+
decoder_library, frame_time);
182+
total_decoding_time = 0;
183+
start_time = clock(); // We reset, since we dont want the following times to include the first time.
184+
}
185+
186+
frames_decoded++;
187+
188+
if (frames_decoded > ADDITIONAL_FRAMES) {
189+
break;
190+
}
191+
192+
start_time = end_time; // For timing the next frame
193+
}
194+
}
195+
av_packet_unref(packet);
196+
}
197+
198+
end:
199+
if (frames_decoded > 1) {
200+
double avg_subsequent_frame_time = (total_decoding_time - first_frame_time) / (frames_decoded - 1);
201+
printf("Average: %.6f\n",
202+
avg_subsequent_frame_time);
203+
}
204+
205+
av_frame_free(&frame);
206+
av_packet_free(&packet);
207+
avcodec_free_context(&codec_ctx);
208+
avformat_close_input(&format_ctx);
209+
210+
return 0;
211+
}
212+
213+
int main(int argc, char* argv[]) {
214+
if (argc != 4) {
215+
fprintf(stderr, "Usage: %s <input_file> <decoder_library> <seek_frame>\n", argv[0]);
216+
return 1;
217+
}
218+
219+
const char* input_file = argv[1];
220+
const char* decoder_library = argv[2];
221+
int64_t seek_frame = atoll(argv[3]);
222+
223+
decode_frames_with_library(input_file, decoder_library, seek_frame);
224+
225+
return 0;
226+
}

0 commit comments

Comments
 (0)