libavformat/ffmpeg를 x264 및 RTP와 동기화하는 데 문제가 있습니다.
-
12-12-2019 - |
문제
저는 라이브 피드를 찍는 스트리밍 소프트웨어를 개발하고 있습니다 다양한 종류의 카메라 및 네트워크를 통한 스트림에서 H.264입니다.이를 위해 x264 인코더를 직접 사용하고 있습니다 ( "zerolatency" 사전 설정) 및 NAL을 사용할 수 있는 대로 공급 libav형식을 사용하여 RTP(궁극적으로 RTSP)로 압축할 수 있습니다.이상적으로는 이 응용 프로그램은 가능한 한 실시간이어야 합니다.대부분의 경우, 이것은 잘 작동하고 있습니다.
그러나 불행하게도 일종의 동기화 문제가 있습니다.클라이언트에서 재생되는 모든 비디오는 몇 가지 부드러운 프레임을 표시하는 것 같습니다. 잠시 멈춘 다음 더 많은 프레임이 이어집니다.반복하다.또한 약 4초의 지연이 있는 것으로 보입니다.이것은 다음과 같이 발생합니다. 내가 시도한 모든 비디오 플레이어 :토템, VLC 및 기본 gstreamer 파이프.
나는 모든 것을 다소 작은 테스트 케이스로 요약했습니다.
#include <stdio.h>
#include <stdint.h>
#include <unistd.h>
#include <x264.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#define WIDTH 640
#define HEIGHT 480
#define FPS 30
#define BITRATE 400000
#define RTP_ADDRESS "127.0.0.1"
#define RTP_PORT 49990
struct AVFormatContext* avctx;
struct x264_t* encoder;
struct SwsContext* imgctx;
uint8_t test = 0x80;
void create_sample_picture(x264_picture_t* picture)
{
// create a frame to store in
x264_picture_alloc(picture, X264_CSP_I420, WIDTH, HEIGHT);
// fake image generation
// disregard how wrong this is; just writing a quick test
int strides = WIDTH / 8;
uint8_t* data = malloc(WIDTH * HEIGHT * 3);
memset(data, test, WIDTH * HEIGHT * 3);
test = (test << 1) | (test >> (8 - 1));
// scale the image
sws_scale(imgctx, (const uint8_t* const*) &data, &strides, 0, HEIGHT,
picture->img.plane, picture->img.i_stride);
}
int encode_frame(x264_picture_t* picture, x264_nal_t** nals)
{
// encode a frame
x264_picture_t pic_out;
int num_nals;
int frame_size = x264_encoder_encode(encoder, nals, &num_nals, picture, &pic_out);
// ignore bad frames
if (frame_size < 0)
{
return frame_size;
}
return num_nals;
}
void stream_frame(uint8_t* payload, int size)
{
// initalize a packet
AVPacket p;
av_init_packet(&p);
p.data = payload;
p.size = size;
p.stream_index = 0;
p.flags = AV_PKT_FLAG_KEY;
p.pts = AV_NOPTS_VALUE;
p.dts = AV_NOPTS_VALUE;
// send it out
av_interleaved_write_frame(avctx, &p);
}
int main(int argc, char* argv[])
{
// initalize ffmpeg
av_register_all();
// set up image scaler
// (in-width, in-height, in-format, out-width, out-height, out-format, scaling-method, 0, 0, 0)
imgctx = sws_getContext(WIDTH, HEIGHT, PIX_FMT_MONOWHITE,
WIDTH, HEIGHT, PIX_FMT_YUV420P,
SWS_FAST_BILINEAR, NULL, NULL, NULL);
// set up encoder presets
x264_param_t param;
x264_param_default_preset(¶m, "ultrafast", "zerolatency");
param.i_threads = 3;
param.i_width = WIDTH;
param.i_height = HEIGHT;
param.i_fps_num = FPS;
param.i_fps_den = 1;
param.i_keyint_max = FPS;
param.b_intra_refresh = 0;
param.rc.i_bitrate = BITRATE;
param.b_repeat_headers = 1; // whether to repeat headers or write just once
param.b_annexb = 1; // place start codes (1) or sizes (0)
// initalize
x264_param_apply_profile(¶m, "high");
encoder = x264_encoder_open(¶m);
// at this point, x264_encoder_headers can be used, but it has had no effect
// set up streaming context. a lot of error handling has been ommitted
// for brevity, but this should be pretty standard.
avctx = avformat_alloc_context();
struct AVOutputFormat* fmt = av_guess_format("rtp", NULL, NULL);
avctx->oformat = fmt;
snprintf(avctx->filename, sizeof(avctx->filename), "rtp://%s:%d", RTP_ADDRESS, RTP_PORT);
if (url_fopen(&avctx->pb, avctx->filename, URL_WRONLY) < 0)
{
perror("url_fopen failed");
return 1;
}
struct AVStream* stream = av_new_stream(avctx, 1);
// initalize codec
AVCodecContext* c = stream->codec;
c->codec_id = CODEC_ID_H264;
c->codec_type = AVMEDIA_TYPE_VIDEO;
c->flags = CODEC_FLAG_GLOBAL_HEADER;
c->width = WIDTH;
c->height = HEIGHT;
c->time_base.den = FPS;
c->time_base.num = 1;
c->gop_size = FPS;
c->bit_rate = BITRATE;
avctx->flags = AVFMT_FLAG_RTP_HINT;
// write the header
av_write_header(avctx);
// make some frames
for (int frame = 0; frame < 10000; frame++)
{
// create a sample moving frame
x264_picture_t* pic = (x264_picture_t*) malloc(sizeof(x264_picture_t));
create_sample_picture(pic);
// encode the frame
x264_nal_t* nals;
int num_nals = encode_frame(pic, &nals);
if (num_nals < 0)
printf("invalid frame size: %d\n", num_nals);
// send out NALs
for (int i = 0; i < num_nals; i++)
{
stream_frame(nals[i].p_payload, nals[i].i_payload);
}
// free up resources
x264_picture_clean(pic);
free(pic);
// stream at approx 30 fps
printf("frame %d\n", frame);
usleep(33333);
}
return 0;
}
이 테스트는 흰색 배경에 검은색 선을 표시합니다. 왼쪽으로 부드럽게 움직여야 합니다.ffmpeg 0.6.5 용으로 작성되었습니다. 그러나 문제는 다음에서 재현 할 수 있습니다. 0.8 그리고 0.10 (지금까지 테스트한 내용에 따르면)이 예제를 다음과 같이 짧게 만들기 위해 오류 처리에서 몇 가지 단축키를 사용했습니다. 여전히 문제를 보여주면서 가능하므로 일부를 양해해 주십시오. 불쾌한 코드.또한 여기서는 SDP를 사용하지 않지만 비슷한 결과로 이미 사용해 보았습니다.테스트는 다음과 같을 수 있습니다. 컴파일 :
gcc -g -std=gnu99 streamtest.c -lswscale -lavformat -lx264 -lm -lpthread -o streamtest
gtreemer로 직접 재생할 수 있습니다.
gst-launch udpsrc port=49990 ! application/x-rtp,payload=96,clock-rate=90000 ! rtph264depay ! decodebin ! xvimagesink
즉시 말더듬을 알아차려야 합니다.내가 가진 하나의 일반적인 "수정" 인터넷을 통해 볼 수 있는 것은 파이프라인에 sync=false 를 추가하는 것입니다.
gst-launch udpsrc port=49990 ! application/x-rtp,payload=96,clock-rate=90000 ! rtph264depay ! decodebin ! xvimagesink sync=false
이로 인해 재생이 매끄럽게(그리고 거의 실시간) 발생하지만 솔루션이 아니며 gstreamer에서만 작동합니다.를 수정하고 싶습니다. 소스에 문제가 있습니다.거의 동일하게 스트리밍할 수 있었습니다. 원시 ffmpeg를 사용하는 매개 변수이며 문제가 없었습니다.
ffmpeg -re -i sample.mp4 -vcodec libx264 -vpre ultrafast -vpre baseline -b 400000 -an -f rtp rtp://127.0.0.1:49990 -an
분명히 내가 뭔가 잘못하고 있습니다.하지만 그것은 무엇입니까?
해결책
1) libx264로 보내는 프레임에 대해 PTS를 설정하지 않았습니다 ( "non-strictly-monotonic PTS"경고가 표시되어야 함). 2) libavformat의 rtp muxer로 보내는 패킷에 대해 PTS / DTS를 설정하지 않았습니다 (설정해야한다고 100 % 확신 할 수는 없지만 더 좋을 것 같습니다.소스 코드에서 보면 rtp가 PTS를 사용하는 것처럼 보입니다.3) IMHO usleep(33333)이 좋지 않습니다.이로 인해 이번에도 인코더가 정지되고(대기 시간 증가) RTP로 전송할 필요가 없더라도 이 시간 동안 다음 프레임을 인코딩할 수 있습니다.
추신그런데 param.rc.i_rc_method를 X264_RC_ABR로 설정하지 않았으므로 libx264는 대신 CRF 23을 사용하고 "param.rc.i_bitrate = BITRATE"를 무시합니다.또한 네트워크 전송을 위해 인코딩할 때 VBV를 사용하는 것이 좋습니다.