/* 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 #include #include #include #include // 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 #include #include } #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 videoStreams; std::vector audioStreams; for(unsigned i=0; inb_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& 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(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 &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(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& 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(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(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::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& FfmpegVideoOutput::Streams() const { return strs; } void FfmpegVideoOutput::SetStreams(const std::vector& str, const std::string& /*uri*/, const picojson::value& properties) { strs.insert(strs.end(), str.begin(), str.end()); for(std::vector::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()) { pango_print_warn("Ignoring attached video properties."); } } int FfmpegVideoOutput::WriteStreams(const unsigned char* data, const picojson::value& /*frame_properties*/) { for(std::vector::iterator i = streams.begin(); i!= streams.end(); ++i) { FfmpegVideoOutputStream& s = **i; Image img = s.GetStreamInfo().StreamImage(data); s.WriteImage(img.ptr, img.w, img.h); } return frame_count++; } PANGOLIN_REGISTER_FACTORY(FfmpegVideo) { struct FfmpegVideoFactory : public FactoryInterface { std::unique_ptr Open(const Uri& uri) override { const std::array 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(); } } std::string outfmt = uri.Get("fmt","RGB24"); ToUpper(outfmt); const int video_stream = uri.Get("stream",-1); return std::unique_ptr( new FfmpegVideo(uri.url.c_str(), outfmt, "", false, video_stream) ); }else if( !uri.scheme.compare("v4lmjpeg")) { const int video_stream = uri.Get("stream",-1); const ImageDim size = uri.Get("size",ImageDim(0,0)); return std::unique_ptr( new FfmpegVideo(uri.url.c_str(),"RGB24", "video4linux", false, video_stream, size) ); } else if( !uri.scheme.compare("mjpeg")) { return std::unique_ptr( new FfmpegVideo(uri.url.c_str(),"RGB24", "MJPEG" ) ); }else if( !uri.scheme.compare("convert") ) { std::string outfmt = uri.Get("fmt","RGB24"); ToUpper(outfmt); std::unique_ptr subvid = pangolin::OpenVideo(uri.url); return std::unique_ptr( new FfmpegConverter(subvid,outfmt,FFMPEG_POINT) ); }else{ return std::unique_ptr(); } } }; auto factory = std::make_shared(); FactoryRegistry::I().RegisterFactory(factory, 10, "ffmpeg"); FactoryRegistry::I().RegisterFactory(factory, 10, "v4lmjpeg"); FactoryRegistry::I().RegisterFactory(factory, 10, "mjpeg"); FactoryRegistry::I().RegisterFactory(factory, 20, "convert"); FactoryRegistry::I().RegisterFactory(factory, 15, "file"); FactoryRegistry::I().RegisterFactory(factory, 15, "files"); } PANGOLIN_REGISTER_FACTORY(FfmpegVideoOutput) { struct FfmpegVideoFactory final : public FactoryInterface { std::unique_ptr 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( new FfmpegVideoOutput(filename, desired_frame_rate, desired_bit_rate, flip) ); } }; auto factory = std::make_shared(); FactoryRegistry::I().RegisterFactory(factory, 10, "ffmpeg"); } }