This commit is contained in:
PodmogilnyjIvan
2021-12-03 03:34:31 -08:00
commit ff4acf84be
542 changed files with 136810 additions and 0 deletions

View File

@@ -0,0 +1,953 @@
/* This file is part of the Pangolin Project.
* http://github.com/stevenlovegrove/Pangolin
*
* Copyright (c) 2011 Steven Lovegrove
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
#include <array>
#include <pangolin/factory/factory_registry.h>
#include <pangolin/video/iostream_operators.h>
#include <pangolin/utils/file_utils.h>
#include <pangolin/video/drivers/ffmpeg.h>
// Some versions of FFMPEG define this horrid macro in global scope.
#undef PixelFormat
// It is impossible to keep up with ffmpeg deprecations, so ignore these warnings.
#if defined(_GCC_) || defined(_CLANG_)
# pragma GCC diagnostic ignored "-Wdeprecated"
#endif
extern "C"
{
#include <libavformat/avio.h>
#include <libavutil/mathematics.h>
#include <libavdevice/avdevice.h>
}
#define CODEC_FLAG_GLOBAL_HEADER AV_CODEC_FLAG_GLOBAL_HEADER
namespace pangolin
{
#ifdef HAVE_FFMPEG_AVPIXELFORMAT
# define TEST_PIX_FMT_RETURN(fmt) case AV_PIX_FMT_##fmt: return #fmt;
#else
# define AV_PIX_FMT_NONE PIX_FMT_NONE
# define AV_PIX_FMT_GRAY8 PIX_FMT_GRAY8
# define TEST_PIX_FMT_RETURN(fmt) case PIX_FMT_##fmt: return #fmt;
#endif
AVPixelFormat FfmpegFmtFromString(const std::string fmt)
{
const std::string lfmt = ToLowerCopy(fmt);
if(!lfmt.compare("gray8") || !lfmt.compare("grey8") || !lfmt.compare("grey")) {
return AV_PIX_FMT_GRAY8;
}
return av_get_pix_fmt(lfmt.c_str());
}
std::string FfmpegFmtToString(const AVPixelFormat fmt)
{
switch( fmt )
{
TEST_PIX_FMT_RETURN(YUV420P);
TEST_PIX_FMT_RETURN(YUYV422);
TEST_PIX_FMT_RETURN(RGB24);
TEST_PIX_FMT_RETURN(BGR24);
TEST_PIX_FMT_RETURN(YUV422P);
TEST_PIX_FMT_RETURN(YUV444P);
TEST_PIX_FMT_RETURN(YUV410P);
TEST_PIX_FMT_RETURN(YUV411P);
TEST_PIX_FMT_RETURN(GRAY8);
TEST_PIX_FMT_RETURN(MONOWHITE);
TEST_PIX_FMT_RETURN(MONOBLACK);
TEST_PIX_FMT_RETURN(PAL8);
TEST_PIX_FMT_RETURN(YUVJ420P);
TEST_PIX_FMT_RETURN(YUVJ422P);
TEST_PIX_FMT_RETURN(YUVJ444P);
#ifdef FF_API_XVMC
TEST_PIX_FMT_RETURN(XVMC_MPEG2_MC);
TEST_PIX_FMT_RETURN(XVMC_MPEG2_IDCT);
#endif
TEST_PIX_FMT_RETURN(UYVY422);
TEST_PIX_FMT_RETURN(UYYVYY411);
TEST_PIX_FMT_RETURN(BGR8);
TEST_PIX_FMT_RETURN(BGR4);
TEST_PIX_FMT_RETURN(BGR4_BYTE);
TEST_PIX_FMT_RETURN(RGB8);
TEST_PIX_FMT_RETURN(RGB4);
TEST_PIX_FMT_RETURN(RGB4_BYTE);
TEST_PIX_FMT_RETURN(NV12);
TEST_PIX_FMT_RETURN(NV21);
TEST_PIX_FMT_RETURN(ARGB);
TEST_PIX_FMT_RETURN(RGBA);
TEST_PIX_FMT_RETURN(ABGR);
TEST_PIX_FMT_RETURN(BGRA);
TEST_PIX_FMT_RETURN(GRAY16BE);
TEST_PIX_FMT_RETURN(GRAY16LE);
TEST_PIX_FMT_RETURN(YUV440P);
TEST_PIX_FMT_RETURN(YUVJ440P);
TEST_PIX_FMT_RETURN(YUVA420P);
#ifdef FF_API_VDPAU
TEST_PIX_FMT_RETURN(VDPAU_H264);
TEST_PIX_FMT_RETURN(VDPAU_MPEG1);
TEST_PIX_FMT_RETURN(VDPAU_MPEG2);
TEST_PIX_FMT_RETURN(VDPAU_WMV3);
TEST_PIX_FMT_RETURN(VDPAU_VC1);
#endif
TEST_PIX_FMT_RETURN(RGB48BE );
TEST_PIX_FMT_RETURN(RGB48LE );
TEST_PIX_FMT_RETURN(RGB565BE);
TEST_PIX_FMT_RETURN(RGB565LE);
TEST_PIX_FMT_RETURN(RGB555BE);
TEST_PIX_FMT_RETURN(RGB555LE);
TEST_PIX_FMT_RETURN(BGR565BE);
TEST_PIX_FMT_RETURN(BGR565LE);
TEST_PIX_FMT_RETURN(BGR555BE);
TEST_PIX_FMT_RETURN(BGR555LE);
TEST_PIX_FMT_RETURN(VAAPI_MOCO);
TEST_PIX_FMT_RETURN(VAAPI_IDCT);
TEST_PIX_FMT_RETURN(VAAPI_VLD);
TEST_PIX_FMT_RETURN(YUV420P16LE);
TEST_PIX_FMT_RETURN(YUV420P16BE);
TEST_PIX_FMT_RETURN(YUV422P16LE);
TEST_PIX_FMT_RETURN(YUV422P16BE);
TEST_PIX_FMT_RETURN(YUV444P16LE);
TEST_PIX_FMT_RETURN(YUV444P16BE);
#ifdef FF_API_VDPAU
TEST_PIX_FMT_RETURN(VDPAU_MPEG4);
#endif
TEST_PIX_FMT_RETURN(DXVA2_VLD);
TEST_PIX_FMT_RETURN(RGB444BE);
TEST_PIX_FMT_RETURN(RGB444LE);
TEST_PIX_FMT_RETURN(BGR444BE);
TEST_PIX_FMT_RETURN(BGR444LE);
TEST_PIX_FMT_RETURN(Y400A );
TEST_PIX_FMT_RETURN(NB );
default: return "";
}
}
#undef TEST_PIX_FMT_RETURN
FfmpegVideo::FfmpegVideo(const std::string filename, const std::string strfmtout, const std::string codec_hint, bool dump_info, int user_video_stream, ImageDim size)
:pFormatCtx(0)
{
InitUrl(filename, strfmtout, codec_hint, dump_info, user_video_stream, size);
}
void FfmpegVideo::InitUrl(const std::string url, const std::string strfmtout, const std::string codec_hint, bool dump_info, int user_video_stream, ImageDim size)
{
if( url.find('*') != url.npos )
throw VideoException("Wildcards not supported. Please use ffmpegs printf style formatting for image sequences. e.g. img-000000%04d.ppm");
// Register all formats and codecs
av_register_all();
// Register all devices
avdevice_register_all();
AVInputFormat* fmt = NULL;
if( !codec_hint.empty() ) {
fmt = av_find_input_format(codec_hint.c_str());
}
#if (LIBAVFORMAT_VERSION_MAJOR >= 53)
AVDictionary* options = nullptr;
if(size.x != 0 && size.y != 0) {
std::string s = std::to_string(size.x) + "x" + std::to_string(size.y);
av_dict_set(&options, "video_size", s.c_str(), 0);
}
if( avformat_open_input(&pFormatCtx, url.c_str(), fmt, &options) )
#else
// Deprecated - can't use with mjpeg
if( av_open_input_file(&pFormatCtx, url.c_str(), fmt, 0, NULL) )
#endif
throw VideoException("Couldn't open stream");
if( !ToLowerCopy(codec_hint).compare("mjpeg") )
#ifdef HAVE_FFMPEG_MAX_ANALYZE_DURATION2
pFormatCtx->max_analyze_duration2 = AV_TIME_BASE * 0.0;
#else
pFormatCtx->max_analyze_duration = AV_TIME_BASE * 0.0;
#endif
// Retrieve stream information
#if (LIBAVFORMAT_VERSION_MAJOR >= 53)
if(avformat_find_stream_info(pFormatCtx, 0)<0)
#else
// Deprecated
if(av_find_stream_info(pFormatCtx)<0)
#endif
throw VideoException("Couldn't find stream information");
if(dump_info) {
// Dump information about file onto standard error
#if (LIBAVFORMAT_VERSION_MAJOR >= 53)
av_dump_format(pFormatCtx, 0, url.c_str(), false);
#else
// Deprecated
dump_format(pFormatCtx, 0, url.c_str(), false);
#endif
}
// Find the first video stream
videoStream=-1;
audioStream=-1;
std::vector<int> videoStreams;
std::vector<int> audioStreams;
for(unsigned i=0; i<pFormatCtx->nb_streams; i++)
{
if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO)
{
videoStreams.push_back(i);
}else if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_AUDIO)
{
audioStreams.push_back(i);
}
}
if(videoStreams.size()==0)
throw VideoException("Couldn't find a video stream");
if(0 <= user_video_stream && user_video_stream < (int)videoStreams.size() ) {
videoStream = videoStreams[user_video_stream];
}else{
videoStream = videoStreams[0];
}
// Get a pointer to the codec context for the video stream
pVidCodecCtx = pFormatCtx->streams[videoStream]->codec;
// Find the decoder for the video stream
pVidCodec=avcodec_find_decoder(pVidCodecCtx->codec_id);
if(pVidCodec==0)
throw VideoException("Codec not found");
// Open video codec
#if LIBAVCODEC_VERSION_MAJOR > 52
if(avcodec_open2(pVidCodecCtx, pVidCodec,0)<0)
#else
if(avcodec_open(pVidCodecCtx, pVidCodec)<0)
#endif
throw VideoException("Could not open codec");
// Hack to correct wrong frame rates that seem to be generated by some codecs
if(pVidCodecCtx->time_base.num>1000 && pVidCodecCtx->time_base.den==1)
pVidCodecCtx->time_base.den=1000;
// Allocate video frames
#if LIBAVUTIL_VERSION_MAJOR >= 54
pFrame = av_frame_alloc();
pFrameOut = av_frame_alloc();
#else
// deprecated
pFrame = avcodec_alloc_frame();
pFrameOut = avcodec_alloc_frame();
#endif
if(!pFrame || !pFrameOut)
throw VideoException("Couldn't allocate frames");
fmtout = FfmpegFmtFromString(strfmtout);
if(fmtout == AV_PIX_FMT_NONE )
throw VideoException("Output format not recognised",strfmtout);
// Image dimensions
const int w = pVidCodecCtx->width;
const int h = pVidCodecCtx->height;
// Determine required buffer size and allocate buffer
numBytesOut=avpicture_get_size(fmtout, w, h);
buffer= new uint8_t[numBytesOut];
// Assign appropriate parts of buffer to image planes in pFrameRGB
avpicture_fill((AVPicture *)pFrameOut, buffer, fmtout, w, h);
// Allocate SWS for converting pixel formats
img_convert_ctx = sws_getContext(w, h,
pVidCodecCtx->pix_fmt,
w, h, fmtout, FFMPEG_POINT,
NULL, NULL, NULL);
if(img_convert_ctx == NULL) {
throw VideoException("Cannot initialize the conversion context");
}
// Populate stream info for users to query
const PixelFormat strm_fmt = PixelFormatFromString(FfmpegFmtToString(fmtout));
const StreamInfo stream(strm_fmt, w, h, (w*strm_fmt.bpp)/8, 0);
streams.push_back(stream);
}
FfmpegVideo::~FfmpegVideo()
{
// Free the RGB image
delete[] buffer;
av_free(pFrameOut);
// Free the YUV frame
av_free(pFrame);
// Close the codec
avcodec_close(pVidCodecCtx);
// Close the video file
#if (LIBAVFORMAT_VERSION_MAJOR >= 54 || (LIBAVFORMAT_VERSION_MAJOR >= 53 && LIBAVFORMAT_VERSION_MINOR >= 21) )
avformat_close_input(&pFormatCtx);
#else
// Deprecated
av_close_input_file(pFormatCtx);
#endif
// Free pixel conversion context
sws_freeContext(img_convert_ctx);
}
const std::vector<StreamInfo>& FfmpegVideo::Streams() const
{
return streams;
}
size_t FfmpegVideo::SizeBytes() const
{
return numBytesOut;
}
void FfmpegVideo::Start()
{
}
void FfmpegVideo::Stop()
{
}
bool FfmpegVideo::GrabNext(unsigned char* image, bool /*wait*/)
{
int gotFrame = 0;
while(!gotFrame && av_read_frame(pFormatCtx, &packet)>=0)
{
// Is this a packet from the video stream?
if(packet.stream_index==videoStream)
{
// Decode video frame
avcodec_decode_video2(pVidCodecCtx, pFrame, &gotFrame, &packet);
}
// Did we get a video frame?
if(gotFrame) {
sws_scale(img_convert_ctx, pFrame->data, pFrame->linesize, 0, pVidCodecCtx->height, pFrameOut->data, pFrameOut->linesize);
memcpy(image,pFrameOut->data[0],numBytesOut);
}
// Free the packet that was allocated by av_read_frame
av_free_packet(&packet);
}
return gotFrame;
}
bool FfmpegVideo::GrabNewest(unsigned char *image, bool wait)
{
return GrabNext(image,wait);
}
void FfmpegConverter::ConvertContext::convert(const unsigned char* src, unsigned char* dst)
{
// avpicture_fill expects uint8_t* w/o const as the second parameter in earlier versions
avpicture_fill((AVPicture*)avsrc, const_cast<unsigned char*>(src + src_buffer_offset), fmtsrc, w, h);
avpicture_fill((AVPicture*)avdst, dst + dst_buffer_offset, fmtdst, w, h);
sws_scale( img_convert_ctx,
avsrc->data, avsrc->linesize, 0, h,
avdst->data, avdst->linesize );
}
FfmpegConverter::FfmpegConverter(std::unique_ptr<VideoInterface> &videoin_, const std::string sfmtdst, FfmpegMethod method )
:videoin(std::move(videoin_))
{
if( !videoin )
throw VideoException("Source video interface not specified");
input_buffer = std::unique_ptr<unsigned char[]>(new unsigned char[videoin->SizeBytes()]);
converters.resize(videoin->Streams().size());
dst_buffer_size = 0;
for(size_t i=0; i < videoin->Streams().size(); ++i) {
const StreamInfo instrm = videoin->Streams()[i];
converters[i].w=instrm.Width();
converters[i].h=instrm.Height();
converters[i].fmtdst = FfmpegFmtFromString(sfmtdst);
converters[i].fmtsrc = FfmpegFmtFromString(instrm.PixFormat());
converters[i].img_convert_ctx = sws_getContext(
instrm.Width(), instrm.Height(), converters[i].fmtsrc,
instrm.Width(), instrm.Height(), converters[i].fmtdst,
method, NULL, NULL, NULL
);
if(!converters[i].img_convert_ctx)
throw VideoException("Could not create SwScale context for pixel conversion");
converters[i].dst_buffer_offset=dst_buffer_size;
converters[i].src_buffer_offset=instrm.Offset() - (unsigned char*)0;
//converters[i].src_buffer_offset=src_buffer_size;
#if LIBAVUTIL_VERSION_MAJOR >= 54
converters[i].avsrc = av_frame_alloc();
converters[i].avdst = av_frame_alloc();
#else
// deprecated
converters[i].avsrc = avcodec_alloc_frame();
converters[i].avdst = avcodec_alloc_frame();
#endif
const PixelFormat pxfmtdst = PixelFormatFromString(sfmtdst);
const StreamInfo sdst( pxfmtdst, instrm.Width(), instrm.Height(), (instrm.Width()*pxfmtdst.bpp)/8, (unsigned char*)0 + converters[i].dst_buffer_offset );
streams.push_back(sdst);
//src_buffer_size += instrm.SizeBytes();
dst_buffer_size += avpicture_get_size(converters[i].fmtdst, instrm.Width(), instrm.Height());
}
}
FfmpegConverter::~FfmpegConverter()
{
for(ConvertContext&c:converters)
{
av_free(c.avsrc);
av_free(c.avdst);
}
}
void FfmpegConverter::Start()
{
// No-Op
}
void FfmpegConverter::Stop()
{
// No-Op
}
size_t FfmpegConverter::SizeBytes() const
{
return dst_buffer_size;
}
const std::vector<StreamInfo>& FfmpegConverter::Streams() const
{
return streams;
}
bool FfmpegConverter::GrabNext( unsigned char* image, bool wait )
{
if( videoin->GrabNext(input_buffer.get(),wait) )
{
for(ConvertContext&c:converters) {
c.convert(input_buffer.get(),image);
}
return true;
}
return false;
}
bool FfmpegConverter::GrabNewest( unsigned char* image, bool wait )
{
if( videoin->GrabNewest(input_buffer.get(),wait) )
{
for(ConvertContext&c:converters) {
c.convert(input_buffer.get(),image);
}
return true;
}
return false;
}
// Based on this example
// http://cekirdek.pardus.org.tr/~ismail/ffmpeg-docs/output-example_8c-source.html
static AVStream* CreateStream(AVFormatContext *oc, CodecID codec_id, uint64_t frame_rate, int bit_rate, AVPixelFormat EncoderFormat, int width, int height)
{
AVCodec* codec = avcodec_find_encoder(codec_id);
if (!(codec)) throw
VideoException("Could not find encoder");
#if (LIBAVFORMAT_VERSION_MAJOR >= 54 || (LIBAVFORMAT_VERSION_MAJOR >= 53 && LIBAVFORMAT_VERSION_MINOR >= 21) )
AVStream* stream = avformat_new_stream(oc, codec);
#else
AVStream* stream = av_new_stream(oc, codec_id);
#endif
if (!stream) throw VideoException("Could not allocate stream");
stream->id = oc->nb_streams-1;
switch (codec->type) {
// case AVMEDIA_TYPE_AUDIO:
// stream->id = 1;
// stream->codec->sample_fmt = AV_SAMPLE_FMT_S16;
// stream->codec->bit_rate = 64000;
// stream->codec->sample_rate = 44100;
// stream->codec->channels = 2;
// break;
case AVMEDIA_TYPE_VIDEO:
stream->codec->codec_id = codec_id;
stream->codec->bit_rate = bit_rate;
stream->codec->width = width;
stream->codec->height = height;
stream->codec->time_base.num = 1;
stream->codec->time_base.den = frame_rate;
stream->codec->gop_size = 12;
stream->codec->pix_fmt = EncoderFormat;
break;
default:
break;
}
/* Some formats want stream headers to be separate. */
if (oc->oformat->flags & AVFMT_GLOBALHEADER)
stream->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
/* open the codec */
int ret = avcodec_open2(stream->codec, codec, NULL);
if (ret < 0) throw VideoException("Could not open video codec");
return stream;
}
class FfmpegVideoOutputStream
{
public:
FfmpegVideoOutputStream(FfmpegVideoOutput& recorder, CodecID codec_id, uint64_t frame_rate, int bit_rate, const StreamInfo& input_info, bool flip );
~FfmpegVideoOutputStream();
const StreamInfo& GetStreamInfo() const;
void WriteImage(const uint8_t* img, int w, int h, double time);
void Flush();
protected:
void WriteAvPacket(AVPacket* pkt);
void WriteFrame(AVFrame* frame);
double BaseFrameTime();
FfmpegVideoOutput& recorder;
StreamInfo input_info;
AVPixelFormat input_format;
AVPixelFormat output_format;
AVPicture src_picture;
AVPicture dst_picture;
int64_t last_pts;
// These pointers are owned by class
AVStream* stream;
SwsContext *sws_ctx;
AVFrame* frame;
bool flip;
};
void FfmpegVideoOutputStream::WriteAvPacket(AVPacket* pkt)
{
if (pkt->size) {
pkt->stream_index = stream->index;
int64_t pts = pkt->pts;
/* convert unit from CODEC's timestamp to stream's one */
#define C2S(field) \
do { \
if (pkt->field != (int64_t) AV_NOPTS_VALUE) \
pkt->field = av_rescale_q(pkt->field, \
stream->codec->time_base, \
stream->time_base); \
} while (0)
C2S(pts);
C2S(dts);
C2S(duration);
#undef C2S
int ret = av_interleaved_write_frame(recorder.oc, pkt);
if (ret < 0) throw VideoException("Error writing video frame");
if(pkt->pts != (int64_t)AV_NOPTS_VALUE) last_pts = pts;
}
}
void FfmpegVideoOutputStream::WriteFrame(AVFrame* frame)
{
AVPacket pkt;
pkt.data = NULL;
pkt.size = 0;
av_init_packet(&pkt);
int ret;
int got_packet = 1;
#if FF_API_LAVF_FMT_RAWPICTURE
// Setup AVPacket
if (recorder.oc->oformat->flags & AVFMT_RAWPICTURE) {
/* Raw video case - directly store the picture in the packet */
pkt.flags |= AV_PKT_FLAG_KEY;
pkt.data = frame->data[0];
pkt.size = sizeof(AVPicture);
pkt.pts = frame->pts;
ret = 0;
} else {
#else
{
#endif
/* encode the image */
#if (LIBAVFORMAT_VERSION_MAJOR >= 54)
ret = avcodec_encode_video2(stream->codec, &pkt, frame, &got_packet);
#else
// TODO: Why is ffmpeg so fussy about this buffer size?
// Making this too big results in garbled output.
// Too small and it will fail entirely.
pkt.size = 50* FF_MIN_BUFFER_SIZE; //std::max(FF_MIN_BUFFER_SIZE, frame->width * frame->height * 4 );
// TODO: Make sure this is being freed by av_free_packet
pkt.data = (uint8_t*) malloc(pkt.size);
pkt.pts = frame->pts;
ret = avcodec_encode_video(stream->codec, pkt.data, pkt.size, frame);
got_packet = ret > 0;
#endif
if (ret < 0) throw VideoException("Error encoding video frame");
}
if (ret >= 0 && got_packet) {
WriteAvPacket(&pkt);
}
av_free_packet(&pkt);
}
void FfmpegVideoOutputStream::WriteImage(const uint8_t* img, int w, int h, double time=-1.0)
{
const int64_t pts = (time >= 0) ? time / BaseFrameTime() : ++last_pts;
recorder.StartStream();
AVCodecContext *c = stream->codec;
if(flip) {
// Earlier versions of ffmpeg do not annotate img as const, although it is
avpicture_fill(&src_picture,const_cast<uint8_t*>(img),input_format,w,h);
for(int i=0; i<4; ++i) {
src_picture.data[i] += (h-1) * src_picture.linesize[i];
src_picture.linesize[i] *= -1;
}
}else{
// Earlier versions of ffmpeg do not annotate img as const, although it is
avpicture_fill(&src_picture,const_cast<uint8_t*>(img),input_format,w,h);
}
if (c->pix_fmt != input_format || c->width != w || c->height != h) {
if(!sws_ctx) {
sws_ctx = sws_getCachedContext( sws_ctx,
w, h, input_format,
c->width, c->height, c->pix_fmt,
SWS_BICUBIC, NULL, NULL, NULL
);
if (!sws_ctx) throw VideoException("Could not initialize the conversion context");
}
sws_scale(sws_ctx,
src_picture.data, src_picture.linesize, 0, h,
dst_picture.data, dst_picture.linesize
);
*((AVPicture *)frame) = dst_picture;
} else {
*((AVPicture *)frame) = src_picture;
}
frame->pts = pts;
frame->width = w;
frame->height = h;
WriteFrame(frame);
}
void FfmpegVideoOutputStream::Flush()
{
#if (LIBAVFORMAT_VERSION_MAJOR >= 54)
if (stream->codec->codec->capabilities & AV_CODEC_CAP_DELAY) {
/* some CODECs like H.264 needs flushing buffered frames by encoding NULL frames. */
/* cf. https://www.ffmpeg.org/doxygen/trunk/group__lavc__encoding.html#ga2c08a4729f72f9bdac41b5533c4f2642 */
AVPacket pkt;
pkt.data = NULL;
pkt.size = 0;
av_init_packet(&pkt);
int got_packet = 1;
while (got_packet) {
int ret = avcodec_encode_video2(stream->codec, &pkt, NULL, &got_packet);
if (ret < 0) throw VideoException("Error encoding video frame");
WriteAvPacket(&pkt);
}
av_free_packet(&pkt);
}
#endif
}
const StreamInfo& FfmpegVideoOutputStream::GetStreamInfo() const
{
return input_info;
}
double FfmpegVideoOutputStream::BaseFrameTime()
{
return (double)stream->codec->time_base.num / (double)stream->codec->time_base.den;
}
FfmpegVideoOutputStream::FfmpegVideoOutputStream(
FfmpegVideoOutput& recorder, CodecID codec_id, uint64_t frame_rate,
int bit_rate, const StreamInfo& input_info, bool flip_image
)
: recorder(recorder), input_info(input_info),
input_format(FfmpegFmtFromString(input_info.PixFormat())),
output_format( FfmpegFmtFromString("YUV420P") ),
last_pts(-1), sws_ctx(NULL), frame(NULL), flip(flip_image)
{
stream = CreateStream(recorder.oc, codec_id, frame_rate, bit_rate, output_format, input_info.Width(), input_info.Height() );
// Allocate the encoded raw picture.
int ret = avpicture_alloc(&dst_picture, stream->codec->pix_fmt, stream->codec->width, stream->codec->height);
if (ret < 0) throw VideoException("Could not allocate picture");
// Allocate frame
#if LIBAVUTIL_VERSION_MAJOR >= 54
frame = av_frame_alloc();
#else
// Deprecated
frame = avcodec_alloc_frame();
#endif
}
FfmpegVideoOutputStream::~FfmpegVideoOutputStream()
{
Flush();
if(sws_ctx) {
sws_freeContext(sws_ctx);
}
av_free(frame);
av_free(dst_picture.data[0]);
avcodec_close(stream->codec);
}
FfmpegVideoOutput::FfmpegVideoOutput(const std::string& filename, int base_frame_rate, int bit_rate, bool flip_image)
: filename(filename), started(false), oc(NULL),
frame_count(0), base_frame_rate(base_frame_rate), bit_rate(bit_rate), is_pipe(pangolin::IsPipe(filename)), flip(flip_image)
{
Initialise(filename);
}
FfmpegVideoOutput::~FfmpegVideoOutput()
{
Close();
}
bool FfmpegVideoOutput::IsPipe() const
{
return is_pipe;
}
void FfmpegVideoOutput::Initialise(std::string filename)
{
av_register_all();
#ifdef HAVE_FFMPEG_AVFORMAT_ALLOC_OUTPUT_CONTEXT2
int ret = avformat_alloc_output_context2(&oc, NULL, NULL, filename.c_str());
#else
oc = avformat_alloc_context();
oc->oformat = av_guess_format(NULL, filename.c_str(), NULL);
int ret = oc->oformat ? 0 : -1;
#endif
if (ret < 0 || !oc) {
pango_print_error("Could not deduce output format from file extension: using MPEG.\n");
#ifdef HAVE_FFMPEG_AVFORMAT_ALLOC_OUTPUT_CONTEXT2
ret = avformat_alloc_output_context2(&oc, NULL, "mpeg", filename.c_str());
#else
oc->oformat = av_guess_format("mpeg", filename.c_str(), NULL);
#endif
if (ret < 0 || !oc) throw VideoException("Couldn't create AVFormatContext");
}
/* open the output file, if needed */
if (!(oc->oformat->flags & AVFMT_NOFILE)) {
ret = avio_open(&oc->pb, filename.c_str(), AVIO_FLAG_WRITE);
if (ret < 0) throw VideoException("Could not open '%s'\n", filename);
}
}
void FfmpegVideoOutput::StartStream()
{
if(!started) {
#if (LIBAVFORMAT_VERSION_MAJOR >= 53)
av_dump_format(oc, 0, filename.c_str(), 1);
#else
// Deprecated
dump_format(oc, 0, filename.c_str(), 1);
#endif
/* Write the stream header, if any. */
int ret = avformat_write_header(oc, NULL);
if (ret < 0) throw VideoException("Error occurred when opening output file");
started = true;
}
}
void FfmpegVideoOutput::Close()
{
for(std::vector<FfmpegVideoOutputStream*>::iterator i = streams.begin(); i!=streams.end(); ++i)
{
(*i)->Flush();
delete *i;
}
av_write_trailer(oc);
if (!(oc->oformat->flags & AVFMT_NOFILE)) avio_close(oc->pb);
avformat_free_context(oc);
}
const std::vector<StreamInfo>& FfmpegVideoOutput::Streams() const
{
return strs;
}
void FfmpegVideoOutput::SetStreams(const std::vector<StreamInfo>& str, const std::string& /*uri*/, const picojson::value& properties)
{
strs.insert(strs.end(), str.begin(), str.end());
for(std::vector<StreamInfo>::const_iterator i = str.begin(); i!= str.end(); ++i)
{
streams.push_back( new FfmpegVideoOutputStream(
*this, oc->oformat->video_codec, base_frame_rate, bit_rate, *i, flip
) );
}
if(!properties.is<picojson::null>()) {
pango_print_warn("Ignoring attached video properties.");
}
}
int FfmpegVideoOutput::WriteStreams(const unsigned char* data, const picojson::value& /*frame_properties*/)
{
for(std::vector<FfmpegVideoOutputStream*>::iterator i = streams.begin(); i!= streams.end(); ++i)
{
FfmpegVideoOutputStream& s = **i;
Image<unsigned char> img = s.GetStreamInfo().StreamImage(data);
s.WriteImage(img.ptr, img.w, img.h);
}
return frame_count++;
}
PANGOLIN_REGISTER_FACTORY(FfmpegVideo)
{
struct FfmpegVideoFactory : public FactoryInterface<VideoInterface> {
std::unique_ptr<VideoInterface> Open(const Uri& uri) override {
const std::array<std::string,43> ffmpeg_ext = {{
".3g2",".3gp", ".amv", ".asf", ".avi", ".drc", ".flv", ".f4v",
".f4p", ".f4a", ".f4b", ".gif", ".gifv", ".m4v", ".mkv", ".mng", ".mov", ".qt",
".mp4", ".m4p", ".m4v", ".mpg", ".mp2", ".mpeg", ".mpe", ".mpv", ".mpg", ".mpeg",
".m2v", ".mxf", ".nsv", ".ogv", ".ogg", ".rm", ".rmvb", ".roq", ".svi", ".vob",
".webm", ".wmv", ".yuv", ".h264", ".h265"
}};
if(!uri.scheme.compare("ffmpeg") || !uri.scheme.compare("file") || !uri.scheme.compare("files") )
{
if(!uri.scheme.compare("file") || !uri.scheme.compare("files")) {
const std::string ext = FileLowercaseExtention(uri.url);
if(std::find(ffmpeg_ext.begin(), ffmpeg_ext.end(), ext) == ffmpeg_ext.end()) {
// Don't try to load unknown files without the ffmpeg:// scheme.
return std::unique_ptr<VideoInterface>();
}
}
std::string outfmt = uri.Get<std::string>("fmt","RGB24");
ToUpper(outfmt);
const int video_stream = uri.Get<int>("stream",-1);
return std::unique_ptr<VideoInterface>( new FfmpegVideo(uri.url.c_str(), outfmt, "", false, video_stream) );
}else if( !uri.scheme.compare("v4lmjpeg")) {
const int video_stream = uri.Get<int>("stream",-1);
const ImageDim size = uri.Get<ImageDim>("size",ImageDim(0,0));
return std::unique_ptr<VideoInterface>( new FfmpegVideo(uri.url.c_str(),"RGB24", "video4linux", false, video_stream, size) );
} else if( !uri.scheme.compare("mjpeg")) {
return std::unique_ptr<VideoInterface>( new FfmpegVideo(uri.url.c_str(),"RGB24", "MJPEG" ) );
}else if( !uri.scheme.compare("convert") ) {
std::string outfmt = uri.Get<std::string>("fmt","RGB24");
ToUpper(outfmt);
std::unique_ptr<VideoInterface> subvid = pangolin::OpenVideo(uri.url);
return std::unique_ptr<VideoInterface>( new FfmpegConverter(subvid,outfmt,FFMPEG_POINT) );
}else{
return std::unique_ptr<VideoInterface>();
}
}
};
auto factory = std::make_shared<FfmpegVideoFactory>();
FactoryRegistry<VideoInterface>::I().RegisterFactory(factory, 10, "ffmpeg");
FactoryRegistry<VideoInterface>::I().RegisterFactory(factory, 10, "v4lmjpeg");
FactoryRegistry<VideoInterface>::I().RegisterFactory(factory, 10, "mjpeg");
FactoryRegistry<VideoInterface>::I().RegisterFactory(factory, 20, "convert");
FactoryRegistry<VideoInterface>::I().RegisterFactory(factory, 15, "file");
FactoryRegistry<VideoInterface>::I().RegisterFactory(factory, 15, "files");
}
PANGOLIN_REGISTER_FACTORY(FfmpegVideoOutput)
{
struct FfmpegVideoFactory final : public FactoryInterface<VideoOutputInterface> {
std::unique_ptr<VideoOutputInterface> Open(const Uri& uri) override {
const int desired_frame_rate = uri.Get("fps", 60);
const int desired_bit_rate = uri.Get("bps", 20000*1024);
const bool flip = uri.Get("flip", false);
std::string filename = uri.url;
if(uri.Contains("unique_filename")) {
filename = MakeUniqueFilename(filename);
}
return std::unique_ptr<VideoOutputInterface>(
new FfmpegVideoOutput(filename, desired_frame_rate, desired_bit_rate, flip)
);
}
};
auto factory = std::make_shared<FfmpegVideoFactory>();
FactoryRegistry<VideoOutputInterface>::I().RegisterFactory(factory, 10, "ffmpeg");
}
}