openpilot is an open source driver assistance system. openpilot performs the functions of Automated Lane Centering and Adaptive Cruise Control for over 200 supported car makes and models.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

167 lines
4.2 KiB

#include "tools/clip/recorder/ffmpeg.h"
#include <QDebug>
FFmpegEncoder::FFmpegEncoder(const std::string& outputFile, int width, int height, int fps) {
// Allocate output context
if (avformat_alloc_output_context2(&format_ctx, nullptr, nullptr, outputFile.c_str()) < 0) {
return;
}
// Find H.264 encoder
const AVCodec* codec = avcodec_find_encoder(AV_CODEC_ID_H264);
if (!codec) {
return;
}
// Create video stream
stream = avformat_new_stream(format_ctx, codec);
if (!stream) {
return;
}
// Create codec context
codec_ctx = avcodec_alloc_context3(codec);
if (!codec_ctx) {
return;
}
// Set codec parameters
codec_ctx->codec_id = AV_CODEC_ID_H264;
codec_ctx->width = width;
codec_ctx->height = height;
codec_ctx->time_base = {1, fps};
codec_ctx->framerate = {fps, 1};
codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
codec_ctx->gop_size = 12;
codec_ctx->max_b_frames = 0;
codec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
stream->time_base = codec_ctx->time_base;
// Set encoding options
AVDictionary* opts = nullptr;
av_dict_set(&opts, "preset", "ultrafast", 0);
av_dict_set(&opts, "tune", "zerolatency", 0);
av_dict_set(&opts, "crf", "28", 0);
// Open codec
if (avcodec_open2(codec_ctx, codec, &opts) < 0) {
av_dict_free(&opts);
return;
}
av_dict_free(&opts);
// Copy codec parameters to stream
if (avcodec_parameters_from_context(stream->codecpar, codec_ctx) < 0) {
return;
}
stream->time_base = codec_ctx->time_base;
// Open output file
if (!(format_ctx->oformat->flags & AVFMT_NOFILE)) {
if (avio_open(&format_ctx->pb, outputFile.c_str(), AVIO_FLAG_WRITE) < 0) {
return;
}
}
// Write header
if (avformat_write_header(format_ctx, nullptr) < 0) {
return;
}
// Allocate frame
frame = av_frame_alloc();
if (!frame) {
return;
}
frame->format = AV_PIX_FMT_YUV420P;
frame->width = width;
frame->height = height;
if (av_frame_get_buffer(frame, 0) < 0) {
return;
}
// Create scaling context
sws_ctx = sws_getContext(width, height, AV_PIX_FMT_BGRA,
width, height, AV_PIX_FMT_YUV420P,
SWS_BILINEAR, nullptr, nullptr, nullptr);
if (!sws_ctx) {
return;
}
// Allocate packet
packet = av_packet_alloc();
if (!packet) {
return;
}
initialized = true;
}
FFmpegEncoder::~FFmpegEncoder() {
if (initialized) {
encodeFrame(nullptr);
av_write_trailer(format_ctx);
if (!(format_ctx->oformat->flags & AVFMT_NOFILE) && format_ctx->pb) {
avio_closep(&format_ctx->pb);
}
}
av_frame_free(&frame);
av_packet_free(&packet);
sws_freeContext(sws_ctx);
avcodec_free_context(&codec_ctx);
avformat_free_context(format_ctx);
}
bool FFmpegEncoder::writeFrame(const QImage& image) {
if (!initialized) return false;
// Convert BGRA to YUV420P
uint8_t* inData[1] = { (uint8_t*)image.bits() };
int inLinesize[1] = { image.bytesPerLine() };
sws_scale(sws_ctx, inData, inLinesize, 0, image.height(),
frame->data, frame->linesize);
frame->pts = frame_count; // PTS in codec_ctx->time_base units
return encodeFrame(frame);
}
bool FFmpegEncoder::encodeFrame(AVFrame* input_frame) {
int ret = avcodec_send_frame(codec_ctx, input_frame);
if (ret < 0) {
fprintf(stderr, "Failed to send frame: %d\n", ret);
return false;
}
while (true) {
ret = avcodec_receive_packet(codec_ctx, packet);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
break;
}
if (ret < 0) {
fprintf(stderr, "Error receiving packet: %d\n", ret);
return false;
}
// Set packet timestamps and duration, rescaling if necessary
packet->pts = av_rescale_q(frame_count, codec_ctx->time_base, stream->time_base);
packet->dts = packet->pts; // No B-frames, so DTS = PTS
packet->duration = av_rescale_q(1, codec_ctx->time_base, stream->time_base);
packet->stream_index = stream->index;
ret = av_interleaved_write_frame(format_ctx, packet);
av_packet_unref(packet);
if (ret < 0) {
fprintf(stderr, "Failed to write packet: %d\n", ret);
return false;
}
if (input_frame) {
frame_count++; // Only increment on actual frames, not flushing
}
}
return true;
}