Browse Source
Implemented a universal encoder based on FFMpeg. I'll get it to work with gif too.
Adding other formats should be easy. In fact, recordingformats can be made into something much shorter. Soon™️
tags/v5.0.0
13 changed files with 221 additions and 147 deletions
@ -0,0 +1,138 @@
|
||||
#include "encoder.hpp" |
||||
|
||||
#include <settings.hpp> |
||||
extern "C" { |
||||
#include <libavutil/imgutils.h> |
||||
#include <libavutil/opt.h> |
||||
} |
||||
|
||||
inline void throwAVErr(int ret, std::string section) { |
||||
char err[AV_ERROR_MAX_STRING_SIZE]; |
||||
av_make_error_string(err, AV_ERROR_MAX_STRING_SIZE, ret); |
||||
std::string newString(err); |
||||
throw std::runtime_error("Error during: " + section + ": " + newString); |
||||
} |
||||
|
||||
Encoder::Encoder(QString &targetFile, QSize res) { |
||||
int ret; |
||||
// Format
|
||||
ret = avformat_alloc_output_context2(&fc, NULL, NULL, targetFile.toLocal8Bit().constData()); |
||||
if (ret < 0) throwAVErr(ret, "format alloc"); |
||||
|
||||
// Stream
|
||||
out->st = avformat_new_stream(fc, NULL); |
||||
if (!out->st) throw std::runtime_error("Unable to allocate video context"); |
||||
out->st->id = fc->nb_streams - 1; |
||||
if (fc->oformat->flags & AVFMT_GLOBALHEADER) fc->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; |
||||
av_dump_format(fc, 0, targetFile.toLocal8Bit().constData(), 1); |
||||
|
||||
// Codec
|
||||
if (!fc->oformat->video_codec) throw std::runtime_error("Video codec not found"); |
||||
codec = avcodec_find_encoder(fc->oformat->video_codec); |
||||
if (!codec) throw std::runtime_error("Video codec not found"); |
||||
out->enc = avcodec_alloc_context3(codec); |
||||
if (!out->enc) throw std::runtime_error("Unable to allocate video context"); |
||||
|
||||
int fps = settings::settings().value("recording/framerate", 30).toInt(); |
||||
|
||||
out->enc->codec_id = codec->id; |
||||
|
||||
out->enc->bit_rate = 400000; |
||||
out->enc->width = res.width() % 2 ? res.width() - 1 : res.width(); |
||||
out->enc->height = res.height() % 2 ? res.height() - 1 : res.height(); |
||||
size = QSize(out->enc->width, out->enc->height); |
||||
out->st->time_base = { 1, fps }; |
||||
out->enc->time_base = out->st->time_base; |
||||
|
||||
out->enc->gop_size = 12; |
||||
out->enc->pix_fmt = AV_PIX_FMT_YUV420P; // blaze it
|
||||
if (out->enc->codec_id == AV_CODEC_ID_MPEG2VIDEO) |
||||
out->enc->max_b_frames = 2; |
||||
else if (out->enc->codec_id == AV_CODEC_ID_MPEG1VIDEO) |
||||
out->enc->mb_decision = 2; |
||||
|
||||
|
||||
ret = avcodec_open2(out->enc, codec, NULL); |
||||
if (ret < 0) throwAVErr(ret, "codec open"); |
||||
|
||||
ret = avcodec_parameters_from_context(out->st->codecpar, out->enc); |
||||
if (ret < 0) throwAVErr(ret, "stream opt copy"); |
||||
|
||||
// Frames
|
||||
out->frame = av_frame_alloc(); |
||||
if (!out->frame) { |
||||
throw std::runtime_error("frame alloc"); |
||||
} |
||||
out->frame->format = out->enc->pix_fmt; |
||||
out->frame->width = out->enc->width; |
||||
out->frame->height = out->enc->height; |
||||
ret = av_frame_get_buffer(out->frame, 32); |
||||
if (ret < 0) throwAVErr(ret, "frame buffer alloc"); |
||||
|
||||
// Writer
|
||||
ret = avio_open(&fc->pb, targetFile.toLocal8Bit().constData(), AVIO_FLAG_WRITE); |
||||
if (ret < 0) throwAVErr(ret, "writer open"); |
||||
ret = avformat_write_header(fc, NULL); |
||||
if (ret < 0) throwAVErr(ret, "write header"); |
||||
|
||||
success = true; |
||||
} |
||||
|
||||
void Encoder::setFrameRGB(uint8_t *rgb) { |
||||
int ret = av_frame_make_writable(out->frame); |
||||
if (ret < 0) throwAVErr(ret, "setFrameRGB"); |
||||
int lineSize[1] = { 3 * out->enc->width }; |
||||
out->sws = sws_getCachedContext(out->sws, out->enc->width, out->enc->height, AV_PIX_FMT_RGB24, out->enc->width, |
||||
out->enc->height, (AVPixelFormat)out->frame->format, 0, 0, 0, 0); |
||||
sws_scale(out->sws, (const uint8_t *const *)&rgb, lineSize, 0, out->enc->height, out->frame->data, out->frame->linesize); |
||||
out->frame->pts = out->nextPts++; |
||||
} |
||||
|
||||
Encoder::~Encoder() { |
||||
end(); |
||||
} |
||||
|
||||
bool Encoder::addFrame(QImage frm) { |
||||
if (!success) return false; |
||||
if (frm.size() != size) frm = frm.copy(QRect(QPoint(0, 0), size)); |
||||
if (frm.format() != QImage::Format_RGB888) frm = frm.convertToFormat(QImage::Format_RGB888); |
||||
uint8_t *frameData = (uint8_t *)frm.bits(); |
||||
setFrameRGB(frameData); |
||||
av_init_packet(&pkt); |
||||
int gotPack = 0; |
||||
int ret = avcodec_encode_video2(out->enc, &pkt, out->frame, &gotPack); |
||||
if (ret < 0) { |
||||
av_packet_unref(&pkt); |
||||
throwAVErr(ret, "encode video"); |
||||
} |
||||
|
||||
if (gotPack) { |
||||
av_packet_rescale_ts(&pkt, out->enc->time_base, out->st->time_base); |
||||
pkt.stream_index = out->st->index; |
||||
ret = av_interleaved_write_frame(fc, &pkt); |
||||
} else |
||||
ret = 0; |
||||
av_packet_unref(&pkt); |
||||
if (ret < 0) throwAVErr(ret, "write frame"); |
||||
return true; |
||||
} |
||||
|
||||
bool Encoder::isRunning() { |
||||
return success; |
||||
} |
||||
|
||||
bool Encoder::end() { |
||||
if (!success) { |
||||
goto cleanup; |
||||
} |
||||
|
||||
av_write_trailer(fc); |
||||
cleanup: |
||||
avcodec_free_context(&out->enc); |
||||
av_frame_free(&out->frame); |
||||
sws_freeContext(out->sws); |
||||
delete out; |
||||
if (!(fc->oformat->flags & AVFMT_NOFILE)) avio_closep(&fc->pb); |
||||
avformat_free_context(fc); |
||||
return success; |
||||
} |
@ -1,114 +0,0 @@
|
||||
#include "webmencoder.hpp" |
||||
|
||||
#include <settings.hpp> |
||||
extern "C" { |
||||
#include <libavutil/imgutils.h> |
||||
#include <libavutil/opt.h> |
||||
} |
||||
|
||||
inline void throwAVErr(int ret) { |
||||
char err[AV_ERROR_MAX_STRING_SIZE]; |
||||
av_make_error_string(err, AV_ERROR_MAX_STRING_SIZE, ret); |
||||
std::string newString(err); |
||||
throw std::runtime_error(newString); |
||||
} |
||||
|
||||
WebMEncoder::WebMEncoder(QSize res) { |
||||
codec = avcodec_find_encoder(CODEC); |
||||
if (!codec) throw std::runtime_error("Codec not found"); |
||||
c = avcodec_alloc_context3(codec); |
||||
if (!c) throw std::runtime_error("Unable to allocate video context"); |
||||
c->bit_rate = 400000; |
||||
|
||||
c->width = res.width() % 2 ? res.width() - 1 : res.width(); |
||||
c->height = res.height() % 2 ? res.height() - 1 : res.height(); |
||||
size = QSize(c->width, c->height); |
||||
int fps = settings::settings().value("recording/framerate", 30).toInt(); |
||||
c->time_base = { 1, fps }; |
||||
c->framerate = { fps, 1 }; |
||||
c->gop_size = 10; |
||||
c->max_b_frames = 1; |
||||
c->pix_fmt = AV_PIX_FMT_YUV420P; |
||||
|
||||
int ret = avcodec_open2(c, codec, NULL); |
||||
if (ret < 0) throwAVErr(ret); |
||||
|
||||
frame = av_frame_alloc(); |
||||
if (!frame) { |
||||
fprintf(stderr, "Could not allocate video frame\n"); |
||||
exit(1); |
||||
} |
||||
frame->format = c->pix_fmt; |
||||
frame->width = c->width; |
||||
frame->height = c->height; |
||||
ret = av_image_alloc(frame->data, frame->linesize, c->width, c->height, c->pix_fmt, 32); |
||||
if (!ret) throwAVErr(ret); |
||||
|
||||
success = true; |
||||
} |
||||
|
||||
void WebMEncoder::setFrameRGB(uint8_t *rgb) { |
||||
int lineSize[1] = { 3 * c->width }; |
||||
sws_context = sws_getCachedContext(sws_context, c->width, c->height, AV_PIX_FMT_RGB24, c->width, c->height, |
||||
AV_PIX_FMT_YUV420P, 0, 0, 0, 0); |
||||
sws_scale(sws_context, (const uint8_t *const *)&rgb, lineSize, 0, c->height, frame->data, frame->linesize); |
||||
} |
||||
|
||||
WebMEncoder::~WebMEncoder() { |
||||
end(); |
||||
} |
||||
|
||||
bool WebMEncoder::addFrame(QImage frm) { |
||||
if (!success) return false; |
||||
if (frm.size() != size) frm = frm.copy(QRect(QPoint(0, 0), size)); |
||||
if (frm.format() != QImage::Format_RGB888) frm = frm.convertToFormat(QImage::Format_RGB888); |
||||
uint8_t *frameData = (uint8_t *)frm.bits(); |
||||
setFrameRGB(frameData); |
||||
av_init_packet(&pkt); |
||||
pkt.data = NULL; |
||||
pkt.size = 0; |
||||
int ret = avcodec_send_frame(c, frame); |
||||
if (ret < 0) return false; |
||||
while (ret >= 0) { |
||||
ret = avcodec_receive_packet(c, &pkt); |
||||
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) |
||||
return true; |
||||
else if (ret < 0) { |
||||
return false; |
||||
} |
||||
video.append((const char *)pkt.data, pkt.size); |
||||
av_packet_unref(&pkt); |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
bool WebMEncoder::isRunning() { |
||||
return success; |
||||
} |
||||
|
||||
QByteArray WebMEncoder::end() { |
||||
int ret; |
||||
uint8_t endcode[] = { 0, 0, 1, 0xb7 }; |
||||
if (!success) { |
||||
goto cleanup; |
||||
} |
||||
ret = avcodec_send_frame(c, frame); |
||||
while (ret >= 0) { |
||||
ret = avcodec_receive_packet(c, &pkt); |
||||
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) |
||||
break; |
||||
else if (ret < 0) |
||||
break; |
||||
video.append((const char *)pkt.data, pkt.size); |
||||
av_packet_unref(&pkt); |
||||
} |
||||
video.append((const char *)endcode, sizeof(endcode)); |
||||
cleanup: |
||||
if (c) { |
||||
avcodec_close(c); |
||||
avcodec_free_context(&c); |
||||
} |
||||
if (frame) av_frame_free(&frame); |
||||
av_packet_unref(&pkt); |
||||
return video; |
||||
} |
Loading…
Reference in new issue