parent
f6b5ec61d7
commit
ea9deb3b9f
@ -0,0 +1,117 @@
|
|||||||
|
#include <QDebug>
|
||||||
|
|
||||||
|
#include "recorder.h"
|
||||||
|
|
||||||
|
Recorder::Recorder(const QString& fileName, const QSize& declaredFrameSize)
|
||||||
|
: m_fileName(fileName)
|
||||||
|
, m_declaredFrameSize(declaredFrameSize)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Recorder::~Recorder()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Recorder::open(AVCodec *inputCodec)
|
||||||
|
{
|
||||||
|
const AVOutputFormat* mp4 = findMp4Muxer();
|
||||||
|
if (!mp4) {
|
||||||
|
qCritical("Could not find mp4 muxer");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_formatCtx = avformat_alloc_context();
|
||||||
|
if (!m_formatCtx) {
|
||||||
|
qCritical("Could not allocate output context");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// contrary to the deprecated API (av_oformat_next()), av_muxer_iterate()
|
||||||
|
// returns (on purpose) a pointer-to-const, but AVFormatContext.oformat
|
||||||
|
// still expects a pointer-to-non-const (it has not be updated accordingly)
|
||||||
|
// <https://github.com/FFmpeg/FFmpeg/commit/0694d8702421e7aff1340038559c438b61bb30dd>
|
||||||
|
|
||||||
|
m_formatCtx->oformat = (AVOutputFormat*)mp4;
|
||||||
|
|
||||||
|
AVStream* outStream = avformat_new_stream(m_formatCtx, inputCodec);
|
||||||
|
if (!outStream) {
|
||||||
|
avformat_free_context(m_formatCtx);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// In ffmpeg/doc/APIchanges:
|
||||||
|
// 2016-04-11 - 6f69f7a / 9200514 - lavf 57.33.100 / 57.5.0 - avformat.h
|
||||||
|
// Add AVStream.codecpar, deprecate AVStream.codec.
|
||||||
|
#if (LIBAVFORMAT_VERSION_MICRO >= 100 /* FFmpeg */ && \
|
||||||
|
LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(57, 33, 100)) \
|
||||||
|
|| (LIBAVFORMAT_VERSION_MICRO < 100 && /* Libav */ \
|
||||||
|
LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(57, 5, 0))
|
||||||
|
outStream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
|
||||||
|
outStream->codecpar->codec_id = inputCodec->id;
|
||||||
|
outStream->codecpar->format = AV_PIX_FMT_YUV420P;
|
||||||
|
outStream->codecpar->width = m_declaredFrameSize.width();
|
||||||
|
outStream->codecpar->height = m_declaredFrameSize.height();
|
||||||
|
#else
|
||||||
|
outStream->codec->codec_type = AVMEDIA_TYPE_VIDEO;
|
||||||
|
outStream->codec->codec_id = inputCodec->id;
|
||||||
|
outStream->codec->pix_fmt = AV_PIX_FMT_YUV420P;
|
||||||
|
outStream->codec->width = m_declaredFrameSize.width();
|
||||||
|
outStream->codec->height = m_declaredFrameSize.height();
|
||||||
|
#endif
|
||||||
|
// timestamps in us
|
||||||
|
outStream->time_base.num = 1;
|
||||||
|
outStream->time_base.den = 1000000;
|
||||||
|
|
||||||
|
int ret = avio_open(&m_formatCtx->pb, m_fileName.toUtf8().toStdString().c_str(),
|
||||||
|
AVIO_FLAG_WRITE);
|
||||||
|
if (ret < 0) {
|
||||||
|
qCritical(QString("Failed to open output file: %1").arg(m_fileName).toUtf8().toStdString().c_str());
|
||||||
|
// ostream will be cleaned up during context cleaning
|
||||||
|
avformat_free_context(m_formatCtx);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = avformat_write_header(m_formatCtx, Q_NULLPTR);
|
||||||
|
if (ret < 0) {
|
||||||
|
qCritical(QString("Failed to write header to %1").arg(m_fileName).toUtf8().toStdString().c_str());
|
||||||
|
avio_closep(&m_formatCtx->pb);
|
||||||
|
avformat_free_context(m_formatCtx);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Recorder::close()
|
||||||
|
{
|
||||||
|
int ret = av_write_trailer(m_formatCtx);
|
||||||
|
if (ret < 0) {
|
||||||
|
qCritical(QString("Failed to write trailer to %1").arg(m_fileName).toUtf8().toStdString().c_str());
|
||||||
|
}
|
||||||
|
avio_close(m_formatCtx->pb);
|
||||||
|
avformat_free_context(m_formatCtx);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Recorder::write(AVPacket *packet)
|
||||||
|
{
|
||||||
|
return av_write_frame(m_formatCtx, packet) >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AVOutputFormat *Recorder::findMp4Muxer()
|
||||||
|
{
|
||||||
|
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(58, 9, 100)
|
||||||
|
void* opaque = Q_NULLPTR;
|
||||||
|
#endif
|
||||||
|
const AVOutputFormat* outFormat = Q_NULLPTR;
|
||||||
|
do {
|
||||||
|
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(58, 9, 100)
|
||||||
|
outFormat = av_muxer_iterate(&opaque);
|
||||||
|
#else
|
||||||
|
outFormat = av_oformat_next(oformat);
|
||||||
|
#endif
|
||||||
|
// until null or with name "mp4"
|
||||||
|
} while (outFormat && strcmp(outFormat->name, "mp4"));
|
||||||
|
return outFormat;
|
||||||
|
}
|
||||||
@ -0,0 +1,30 @@
|
|||||||
|
#ifndef RECORDER_H
|
||||||
|
#define RECORDER_H
|
||||||
|
#include <QString>
|
||||||
|
#include <QSize>
|
||||||
|
|
||||||
|
extern "C"
|
||||||
|
{
|
||||||
|
#include "libavformat/avformat.h"
|
||||||
|
}
|
||||||
|
|
||||||
|
class Recorder
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Recorder(const QString& fileName, const QSize& declaredFrameSize);
|
||||||
|
virtual ~Recorder();
|
||||||
|
|
||||||
|
bool open(AVCodec* inputCodec);
|
||||||
|
void close();
|
||||||
|
bool write(AVPacket* packet);
|
||||||
|
|
||||||
|
private:
|
||||||
|
const AVOutputFormat* findMp4Muxer();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QString m_fileName = "";
|
||||||
|
AVFormatContext* m_formatCtx = Q_NULLPTR;
|
||||||
|
QSize m_declaredFrameSize;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // RECORDER_H
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
HEADERS += \
|
||||||
|
$$PWD/recorder.h
|
||||||
|
|
||||||
|
SOURCES += \
|
||||||
|
$$PWD/recorder.cpp
|
||||||
Loading…
Reference in new issue