6#include "private/qmultimediautils_p.h"
13#include <private/qplatformcamera_p.h>
14#include <private/qplatformvideosource_p.h>
22#include <libavutil/pixdesc.h>
23#include <libavutil/common.h>
38 formatContext = avformat_alloc_context();
39 formatContext->oformat =
const_cast<AVOutputFormat *
>(avFormat);
42 formatContext->url = (
char *)av_malloc(
encoded.
size() + 1);
44 formatContext->pb =
nullptr;
45 auto result = avio_open2(&formatContext->pb, formatContext->url, AVIO_FLAG_WRITE,
nullptr,
nullptr);
46 qCDebug(qLcFFmpegEncoder) <<
"opened" <<
result << formatContext->url;
48 muxer =
new Muxer(
this);
59 input->setRunning(
true);
64 auto frameFormat =
source->frameFormat();
66 if (!frameFormat.isValid()) {
67 qCWarning(qLcFFmpegEncoder) <<
"Cannot add source; invalid vide frame format";
71 std::optional<AVPixelFormat> hwPixelFormat =
source->ffmpegHWPixelFormat()
72 ? AVPixelFormat(*
source->ffmpegHWPixelFormat())
73 : std::optional<AVPixelFormat>{};
75 qCDebug(qLcFFmpegEncoder) <<
"adding video source" <<
source->metaObject()->className() <<
":"
76 <<
"pixelFormat=" << frameFormat.pixelFormat()
77 <<
"frameSize=" << frameFormat.frameSize()
78 <<
"frameRate=" << frameFormat.frameRate() <<
"ffmpegHWPixelFormat="
79 << (hwPixelFormat ? *hwPixelFormat : AV_PIX_FMT_NONE);
81 auto veUPtr = std::make_unique<VideoEncoder>(
this,
settings, frameFormat, hwPixelFormat);
82 if (veUPtr->isValid()) {
83 auto ve = veUPtr.release();
86 videoEncoders.append(ve);
87 connections.append(conn);
93 qCDebug(qLcFFmpegEncoder) <<
"Encoder::start!";
97 int res = avformat_write_header(formatContext,
nullptr);
99 qWarning() <<
"could not write header, error:" <<
res << err2str(
res);
104 qCDebug(qLcFFmpegEncoder) <<
"stream header is successfully written";
108 audioEncode->start();
109 for (
auto *videoEncoder : videoEncoders)
110 if (videoEncoder->isValid())
111 videoEncoder->start();
116EncodingFinalizer::EncodingFinalizer(Encoder *
e) : encoder(
e) {
120void EncodingFinalizer::run()
122 if (encoder->audioEncode)
123 encoder->audioEncode->kill();
124 for (
auto &videoEncoder : encoder->videoEncoders)
125 videoEncoder->kill();
126 encoder->muxer->kill();
128 int res = av_write_trailer(encoder->formatContext);
132 avformat_free_context(encoder->formatContext);
133 qCDebug(qLcFFmpegEncoder) <<
" done finalizing.";
134 emit encoder->finalizationDone();
138void Encoder::finalize()
140 qCDebug(qLcFFmpegEncoder) <<
">>>>>>>>>>>>>>> finalize";
142 for (
auto &conn : connections)
145 auto *finalizer =
new EncodingFinalizer(
this);
149void Encoder::setPaused(
bool p)
152 audioEncode->setPaused(
p);
153 for (
auto &videoEncoder : videoEncoders)
154 videoEncoder->setPaused(
p);
159 this->metaData = metaData;
164 if (audioEncode && isRecording)
165 audioEncode->addBuffer(
buffer);
171 if (
time > timeRecorded) {
177Muxer::Muxer(Encoder *encoder)
183void Muxer::addPacket(AVPacket *packet)
187 packetQueue.enqueue(packet);
191AVPacket *Muxer::takePacket()
194 if (packetQueue.isEmpty())
197 return packetQueue.dequeue();
202 qCDebug(qLcFFmpegEncoder) <<
"Muxer::init started thread.";
217 auto *packet = takePacket();
220 av_interleaved_write_frame(encoder->formatContext, packet);
224static AVSampleFormat bestMatchingSampleFormat(AVSampleFormat requested,
const AVSampleFormat *available)
229 const AVSampleFormat *
f = available;
230 AVSampleFormat best = *
f;
238 for (; *
f != AV_SAMPLE_FMT_NONE; ++
f) {
239 qCDebug(qLcFFmpegEncoder) <<
"format:" << *
f;
240 if (*
f == requested) {
246 if (av_get_planar_sample_fmt(requested) == *
f) {
258 this->encoder = encoder;
265 Q_ASSERT(avformat_query_codec(encoder->formatContext->oformat, codecID, FF_COMPLIANCE_NORMAL));
274 qCDebug(qLcFFmpegEncoder) <<
"found audio codec" << avCodec->name;
278 AVSampleFormat bestSampleFormat = bestMatchingSampleFormat(requested, avCodec->sample_fmts);
280 stream = avformat_new_stream(encoder->formatContext,
nullptr);
281 stream->id = encoder->formatContext->nb_streams - 1;
282 stream->codecpar->codec_type = AVMEDIA_TYPE_AUDIO;
283 stream->codecpar->codec_id = codecID;
284#if QT_FFMPEG_OLD_CHANNEL_LAYOUT
285 stream->codecpar->channel_layout = av_get_default_channel_layout(
format.channelCount());
288 av_channel_layout_default(&
stream->codecpar->ch_layout,
format.channelCount());
291 stream->codecpar->frame_size = 1024;
292 stream->codecpar->format = bestSampleFormat;
293 stream->time_base = AVRational{ 1,
format.sampleRate() };
295 qCDebug(qLcFFmpegEncoder) <<
"set stream time_base" <<
stream->time_base.num <<
"/"
303 codec = avcodec_alloc_context3(avCodec);
306 qCDebug(qLcFFmpegEncoder) <<
"Most likely, av_format_write_header changed time base from"
307 << 1 <<
"/" <<
format.sampleRate() <<
"to"
308 <<
stream->time_base.num <<
"/" <<
stream->time_base.den;
311 codec->time_base =
stream->time_base;
313 avcodec_parameters_to_context(codec,
stream->codecpar);
315 AVDictionaryHolder opts;
318 int res = avcodec_open2(codec, avCodec, opts);
319 qCDebug(qLcFFmpegEncoder) <<
"audio codec opened" <<
res;
320 qCDebug(qLcFFmpegEncoder) <<
"audio codec params: fmt=" << codec->sample_fmt <<
"rate=" << codec->sample_rate;
322 if (codec->sample_fmt != requested) {
323#if QT_FFMPEG_OLD_CHANNEL_LAYOUT
324 resampler = swr_alloc_set_opts(
nullptr,
325 codec->channel_layout,
328 av_get_default_channel_layout(
format.channelCount()),
334 AVChannelLayout in_ch_layout = {};
335 av_channel_layout_default(&in_ch_layout,
format.channelCount());
336 swr_alloc_set_opts2(&resampler,
337 &codec->ch_layout, codec->sample_fmt, codec->sample_rate,
338 &in_ch_layout, requested,
format.sampleRate(),
358 if (audioBufferQueue.
isEmpty())
360 return audioBufferQueue.
dequeue();
367 input->setFrameSize(codec->frame_size);
369 qCDebug(qLcFFmpegEncoder) <<
"AudioEncoder::init started audio device thread.";
374 while (!audioBufferQueue.
isEmpty())
376 while (avcodec_send_frame(codec,
nullptr) == AVERROR(EAGAIN))
384 return audioBufferQueue.
isEmpty();
387void AudioEncoder::retrievePackets()
390 AVPacket *packet = av_packet_alloc();
391 int ret = avcodec_receive_packet(codec, packet);
393 av_packet_unref(packet);
394 if (
ret != AVERROR(EOF))
396 if (
ret != AVERROR(EAGAIN)) {
398 av_strerror(
ret, errStr, 1024);
399 qCDebug(qLcFFmpegEncoder) <<
"receive packet" <<
ret << errStr;
405 packet->stream_index =
stream->id;
420 frame->format = codec->sample_fmt;
421#if QT_FFMPEG_OLD_CHANNEL_LAYOUT
422 frame->channel_layout = codec->channel_layout;
423 frame->channels = codec->channels;
425 frame->ch_layout = codec->ch_layout;
427 frame->sample_rate = codec->sample_rate;
429 if (
frame->nb_samples)
430 av_frame_get_buffer(
frame.get(), 0);
433 const uint8_t *
data =
buffer.constData<uint8_t>();
439 const auto &timeBase =
stream->time_base;
440 const auto pts = timeBase.den && timeBase.num
441 ? timeBase.den * samplesWritten / (codec->sample_rate * timeBase.num)
444 samplesWritten +=
buffer.frameCount();
452 int ret = avcodec_send_frame(codec,
frame.get());
455 av_strerror(
ret, errStr, 1024);
468 AVPixelFormat ffmpegPixelFormat =
469 hwFormat && *hwFormat != AV_PIX_FMT_NONE ? *hwFormat : swFormat;
493 const bool queueFull = videoFrameQueue.
size() >= maxQueueSize;
496 qCDebug(qLcFFmpegEncoder) <<
"Encoder frame queue full. Frame lost.";
508 return !frameEncoder->
isNull();
516 if (!videoFrameQueue.
isEmpty())
522void VideoEncoder::retrievePackets()
532 qCDebug(qLcFFmpegEncoder) <<
"VideoEncoder::init started video device thread.";
533 bool ok = frameEncoder->
open();
540 while (!videoFrameQueue.
isEmpty())
543 while (frameEncoder->
sendFrame(
nullptr) == AVERROR(EAGAIN))
552 return videoFrameQueue.
isEmpty();
555struct QVideoFrameHolder
561static void freeQVideoFrame(
void *opaque, uint8_t *)
563 delete reinterpret_cast<QVideoFrameHolder *
>(opaque);
573 auto frame = takeFrame();
574 if (!
frame.isValid())
577 if (frameEncoder->
isNull())
588 if (hwFrame && hwFrame->format == frameEncoder->
sourceFormat())
589 avFrame.reset(av_frame_clone(hwFrame));
597 avFrame->width =
size.width();
598 avFrame->height =
size.height();
600 for (
int i = 0;
i < 4; ++
i) {
601 avFrame->data[
i] =
const_cast<uint8_t *
>(
frame.bits(
i));
602 avFrame->linesize[
i] =
frame.bytesPerLine(
i);
609 avFrame->data[0] = (uint8_t *)
img.bits();
610 avFrame->linesize[0] =
img.bytesPerLine();
615 avFrame->opaque_ref = av_buffer_create(
nullptr, 0, freeQVideoFrame,
new QVideoFrameHolder{
frame,
img}, 0);
618 if (baseTime.
loadAcquire() == std::numeric_limits<qint64>::min()) {
621 <<
frame.startTime() << lastFrameTime;
631 qCDebug(qLcFFmpegEncoder) <<
">>> sending frame" << avFrame->pts <<
time << lastFrameTime;
643#include "moc_qffmpegencoder_p.cpp"
T loadAcquire() const noexcept
void storeRelease(T newValue) noexcept
T loadRelaxed() const noexcept
qsizetype size() const noexcept
Returns the number of bytes in this byte array.
const char * constData() const noexcept
Returns a pointer to the const data stored in the byte array.
static AVPixelFormat toAVPixelFormat(QVideoFrameFormat::PixelFormat pixelFormat)
AVFrame * getHWFrame() const
void addBuffer(const QAudioBuffer &buffer)
bool shouldWait() const override
AudioEncoder(Encoder *encoder, QFFmpegAudioInput *input, const QMediaEncoderSettings &settings)
QAtomicInteger< bool > paused
void newTimeStamp(qint64 time)
void error(QMediaRecorder::Error code, const QString &description)
bool shouldWait() const override
void addPacket(AVPacket *)
bool shouldWait() const override
void addFrame(const QVideoFrame &frame)
VideoEncoder(Encoder *encoder, const QMediaEncoderSettings &settings, const QVideoFrameFormat &format, std::optional< AVPixelFormat > hwFormat)
void initWithFormatContext(AVFormatContext *formatContext)
AVPacket * retrievePacket()
const AVRational & getTimeBase() const
qint64 getPts(qint64 ms) const
AVPixelFormat sourceFormat() const
int sendFrame(AVFrameUPtr frame)
qsizetype size() const noexcept
bool isEmpty() const noexcept
void unlock() noexcept
Unlocks this mutex locker.
Q_WEAK_OVERLOAD void setObjectName(const QString &name)
Sets the object's name to name.
void deleteLater()
\threadsafe
void enqueue(const T &t)
Adds value t to the tail of the queue.
T dequeue()
Removes the head item in the queue and returns it.
void finished(QPrivateSignal)
QByteArray toEncoded(FormattingOptions options=FullyEncoded) const
Returns the encoded representation of the URL if it's valid; otherwise an empty QByteArray is returne...
The QVideoFrame class represents a frame of video data.
object setObjectName("A new object name")
AVFrameUPtr makeAVFrame()
const AVCodec * findAVEncoder(AVCodecID codecId, const std::optional< AVHWDeviceType > &deviceType, const std::optional< PixelOrSampleFormat > &format)
QString err2str(int errnum)
void setAVFrameTime(AVFrame &frame, int64_t pts, const AVRational &timeBase)
void applyAudioEncoderOptions(const QMediaEncoderSettings &settings, const QByteArray &codecName, AVCodecContext *codec, AVDictionary **opts)
std::unique_ptr< AVFrame, AVDeleter< decltype(&av_frame_free), &av_frame_free > > AVFrameUPtr
Combined button and popup list for selecting options.
DBusConnection const char DBusError * error
#define Q_LOGGING_CATEGORY(name,...)
#define qCWarning(category,...)
#define qCDebug(category,...)
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLint GLsizei GLsizei GLenum format
GLsizei GLsizei GLchar * source
GLenum GLenum GLenum input
QLatin1StringView QLatin1String
QSettings settings("MySoft", "Star Runner")
[0]
QUrl url("example.com")
[constructor-url-reference]
connect(quitButton, &QPushButton::clicked, &app, &QCoreApplication::quit, Qt::QueuedConnection)
myObject disconnect()
[26]