📄 ffmpeg_video.cpp
字号:
// This file is part of Ambulant Player, www.ambulantplayer.org.//// Copyright (C) 2003-2007 Stichting CWI, // Kruislaan 413, 1098 SJ Amsterdam, The Netherlands.//// Ambulant Player is free software; you can redistribute it and/or modify// it under the terms of the GNU Lesser General Public License as published by// the Free Software Foundation; either version 2.1 of the License, or// (at your option) any later version.//// Ambulant Player is distributed in the hope that it will be useful,// but WITHOUT ANY WARRANTY; without even the implied warranty of// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the// GNU Lesser General Public License for more details.//// You should have received a copy of the GNU Lesser General Public License// along with Ambulant Player; if not, write to the Free Software// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #include <math.h>#include <map>#include "ambulant/net/ffmpeg_video.h" #include "ambulant/net/ffmpeg_audio.h" #include "ambulant/net/ffmpeg_common.h" #include "ambulant/net/ffmpeg_factory.h" #include "ambulant/net/demux_datasource.h" #include "ambulant/lib/logger.h"#include "ambulant/net/url.h"// WARNING: turning on AM_DBG globally for the ffmpeg code seems to trigger// a condition that makes the whole player hang or collapse. So you probably// shouldn't do it:-)//#define AM_DBG#ifndef AM_DBG#define AM_DBG if(0)#endif // How many video frames we would like to buffer at least. This number should// not be too low, otherwise a fast consumer will see only I and P frames// because these are produced before the B frames.// On second thoughts this seems a bad idea, so setting MIN_VIDEO_FRAMES to zero.#define MIN_VIDEO_FRAMES 0// How many video frames we would like to buffer at most.#define MAX_VIDEO_FRAMES 100// This construction is needed to get the CVS version of ffmpeg to work:// AVStream.codec got changed from AVCodecContext to AVCodecContext*#if LIBAVFORMAT_BUILD > 4628 #define am_get_codec_var(codec,var) codec->var #define am_get_codec(codec) codec#else #define am_get_codec_var(codec,var) codec.var #define am_get_codec(codec) &codec#endifusing namespace ambulant;using namespace net;typedef lib::no_arg_callback<ffmpeg_video_decoder_datasource> framedone_callback;video_datasource_factory *ambulant::net::get_ffmpeg_video_datasource_factory(){#if 0 static video_datasource_factory *s_factory; if (!s_factory) s_factory = new ffmpeg_video_datasource_factory(); return s_factory;#else return new ffmpeg_video_datasource_factory();#endif}video_datasource* ffmpeg_video_datasource_factory::new_video_datasource(const net::url& url, timestamp_t clip_begin, timestamp_t clip_end){ AM_DBG lib::logger::get_logger()->debug("ffmpeg_video_datasource_factory::new_video_datasource(%s)", repr(url).c_str()); // First we check that the file format is supported by the file reader. // If it is we create a file reader (demux head end). AVFormatContext *context = ffmpeg_demux::supported(url); if (!context) { AM_DBG lib::logger::get_logger()->trace("ffmpeg: no support for %s", repr(url).c_str()); return NULL; } ffmpeg_demux *thread = new ffmpeg_demux(context, clip_begin, clip_end); // Now, we can check that there is actually video in the file. if (thread->video_stream_nr() < 0) { thread->cancel(); lib::logger::get_logger()->trace("ffmpeg: No video stream in %s", repr(url).c_str()); return NULL; } // Next, if there is video we check that we can decode this type of video // stream. video_format fmt = thread->get_video_format(); //fmt.parameters = (void*) context; AVCodecContext *enc = (AVCodecContext *)fmt.parameters; AM_DBG lib::logger::get_logger()->debug("ffmpeg: Stream type %d, codec_id %d", enc->codec_type, enc->codec_id); if (!ffmpeg_video_decoder_datasource::supported(fmt)) { thread->cancel(); lib::logger::get_logger()->trace("ffmpeg: Unsupported video stream in %s", repr(url).c_str()); return NULL; } // All seems well. Create the demux reader and the decoder. video_datasource *ds = demux_video_datasource::new_demux_video_datasource(url, thread); if (!ds) { lib::logger::get_logger()->debug("ffmpeg_video_datasource_factory::new_video_datasource: could not allocate ffmpeg_demux_video_datasource"); thread->cancel(); return NULL; } video_datasource *dds = new ffmpeg_video_decoder_datasource(ds, fmt); if (dds == NULL) { lib::logger::get_logger()->debug("ffmpeg_video_datasource_factory::new_video_datasource: could not allocate ffmpeg_video_datasource"); thread->cancel(); return NULL; } // Finally, tell the demux datasource to skip ahead to clipBegin, if // it can do so. No harm done if it can't: the decoder will then skip // any unneeded frames. ds->read_ahead(clip_begin); AM_DBG lib::logger::get_logger()->debug("ffmpeg_video_datasource_factory::new_video_datasource (dds = 0x%x)", (void*) dds); return dds; } // **************************** ffmpeg_video_decoder_datasource ********************boolffmpeg_video_decoder_datasource::supported(const video_format& fmt){ if (fmt.name != "ffmpeg") return false; AVCodecContext *enc = (AVCodecContext *)fmt.parameters; if (enc->codec_type != CODEC_TYPE_VIDEO) { AM_DBG lib::logger::get_logger()->debug("ffmpeg_video_datasource_factory::supported: not a video stream !(%d, %d)", enc->codec_type, CODEC_TYPE_VIDEO); return false; } if (avcodec_find_decoder(enc->codec_id) == NULL) { AM_DBG lib::logger::get_logger()->debug("ffmpeg_video_datasource_factory::supported: cannot open video codec (codec_id: %d)", enc->codec_id); return false; } return true;}ffmpeg_video_decoder_datasource::ffmpeg_video_decoder_datasource(video_datasource* src, video_format fmt): m_src(src), m_con(NULL), m_event_processor(NULL), m_client_callback(NULL), m_pts_last_frame(0), m_last_p_pts(0), m_video_clock(0), // XXX Mod by Jack (unsure). Was: src->get_clip_begin() m_frame_count(0), m_dropped_count(0), m_elapsed(0), m_start_input(true){ AM_DBG lib::logger::get_logger()->debug("ffmpeg_video_decoder_datasource::ffmpeg_video_decoder_datasource() (this = 0x%x)", (void*)this); ffmpeg_init(); AM_DBG lib::logger::get_logger()->debug("ffmpeg_video_decoder_datasource: Looking for %s(0x%x) decoder", fmt.name.c_str(), fmt.parameters); if (!_select_decoder(fmt)) lib::logger::get_logger()->error(gettext("ffmpeg_video_decoder_datasource: could not select %s(0x%x) decoder"), fmt.name.c_str(), fmt.parameters); m_fmt = fmt; m_old_frame = ts_pointer_pair(0, NULL);}ffmpeg_video_decoder_datasource::~ffmpeg_video_decoder_datasource(){ AM_DBG lib::logger::get_logger()->debug("ffmpeg_video_decoder_datasource::~ffmpeg_video_decoder_datasource(0x%x)", (void*)this); stop(); if (m_dropped_count) lib::logger::get_logger()->debug("ffmpeg_video_decoder: dropped %d of %d frames", m_dropped_count, m_frame_count);}voidffmpeg_video_decoder_datasource::stop(){ m_lock.enter(); if (m_src) { m_src->stop(); int rem = m_src->release(); if (rem) lib::logger::get_logger()->debug("ffmpeg_video_decoder_datasource::stop(0x%x): m_src refcount=%d", (void*)this, rem); } m_src = NULL; AM_DBG lib::logger::get_logger()->debug("ffmpeg_video_decoder_datasource::stop(0x%x)", (void*)this); if (m_con) { avcodec_close(m_con); AM_DBG lib::logger::get_logger()->debug("ffmpeg_video_decoder_datasource::stop(): avcodec_close(m_con=0x%x) called", m_con); m_con = NULL; } if (m_client_callback) delete m_client_callback; m_client_callback = NULL; // And delete any frames left while ( ! m_frames.empty() ) { _pop_top_frame(); } if (m_old_frame.second) { free(m_old_frame.second); m_old_frame.second = NULL; } m_lock.leave();} boolffmpeg_video_decoder_datasource::has_audio(){ m_lock.enter(); bool rv = m_src && m_src->has_audio(); m_lock.leave(); return rv;}audio_datasource *ffmpeg_video_decoder_datasource::get_audio_datasource(){ m_lock.enter(); audio_datasource *rv = NULL; if (m_src) rv = m_src->get_audio_datasource(); m_lock.leave(); return rv;}void ffmpeg_video_decoder_datasource::start_frame(ambulant::lib::event_processor *evp, ambulant::lib::event *callbackk, timestamp_t timestamp){ m_lock.enter(); AM_DBG lib::logger::get_logger()->debug("ffmpeg_video_decoder_datasource::start_frame: (this = 0x%x)", (void*) this); if (m_client_callback != NULL) { delete m_client_callback; m_client_callback = NULL; lib::logger::get_logger()->error("ffmpeg_video_decoder_datasource::start(): m_client_callback already set!"); } if (m_frames.size() > 0 /* XXXX Check timestamp! */ || _end_of_file() ) { // We have data (or EOF) available. Don't bother starting up our source again, in stead // immedeately signal our client again if (callbackk) { assert(evp); if (timestamp < 0) timestamp = 0; lib::timer::time_type timestamp_milli = timestamp/1000; // micro to milli lib::timer::time_type now_milli = evp->get_timer()->elapsed(); lib::timer::time_type delta_milli = 0; if (now_milli < timestamp_milli) delta_milli = timestamp_milli - now_milli; AM_DBG lib::logger::get_logger()->debug("ffmpeg_video_decoder_datasource::start: trigger client callback timestamp_milli=%d delta_milli=%d, now_milli=%d", (int)timestamp_milli, (int)delta_milli, (int)now_milli); // Sanity check: we don't want this to be more than a second into the future if (delta_milli > 1000) { lib::logger::get_logger()->trace("ffmpeg_video: frame is %f seconds in the future", delta_milli / 1000.0); lib::logger::get_logger()->debug("ffmpeg_video: elapsed()=%dms, timestamp=%dms", now_milli, timestamp_milli); } evp->add_event(callbackk, delta_milli+1, ambulant::lib::ep_high); } else { lib::logger::get_logger()->debug("Internal error: ffmpeg_video_decoder_datasource::start(): no client callback!"); lib::logger::get_logger()->warn(gettext("Programmer error encountered during video playback")); } } else { // We have no data available. Start our source, and in our data available callback we // will signal the client. m_client_callback = callbackk; m_event_processor = evp; } // Don't restart our source if we are at end of file. if ( _end_of_file() ) m_start_input = false; if (m_start_input) { AM_DBG lib::logger::get_logger()->debug("ffmpeg_video_decoder_datasource::start_frame() Calling m_src->start_frame(..)"); lib::event *e = new framedone_callback(this, &ffmpeg_video_decoder_datasource::data_avail); assert(m_src); m_src->start_frame(evp, e, 0); m_start_input = false; } m_lock.leave();}voidprint_frames(sorted_frames frames) { sorted_frames f(frames); while (f.size()) { ts_pointer_pair e = f.top(); printf("e.first=%d ", (int) e.first); f.pop(); } printf("\n"); return;}ts_pointer_pair ffmpeg_video_decoder_datasource::_pop_top_frame() { // pop a frame, return the new top frame // the old top frame is remembered in m_old_frame // old data in m_old_frame is freed. if (m_old_frame.second) { free (m_old_frame.second); m_old_frame.second = NULL; AM_DBG lib::logger::get_logger()->debug("ffmpeg_video_decoder_datasource._pop_top_frame(): free'n m_old_frame.second"); } if (m_frames.empty()) { AM_DBG lib::logger::get_logger()->debug("ffmpeg_video_decoder_datasource._pop_top_frame():no more frames left returning m_old_frame"); return m_old_frame; } m_old_frame = m_frames.top(); m_frames.pop(); return m_frames.top();}void ffmpeg_video_decoder_datasource::frame_done(timestamp_t now, bool keepdata){ m_lock.enter(); if (m_frames.size() == 0) { m_lock.leave(); return; } AM_DBG lib::logger::get_logger()->debug("ffmpeg_video_decoder_datasource.frame_done(%d)", (int)now); while ( m_frames.size() && m_old_frame.first <= now) { AM_DBG lib::logger::get_logger()->debug("ffmpeg_video_decoder_datasource::frame_done: discarding m_old_frame timestamp=%d, now=%d, data ptr = 0x%x",(int)m_old_frame.first,(int)now, m_old_frame.second); _pop_top_frame(); } if (!keepdata) { AM_DBG lib::logger::get_logger()->debug("ffmpeg_video_decoder_datasource::frame_done(%d): free(0x%x)", (int)now, m_old_frame.second); free(m_old_frame.second); m_old_frame.second = NULL; } m_lock.leave();}int ffmpeg_video_decoder_datasource::width(){ m_lock.enter(); _need_fmt_uptodate(); m_lock.leave(); return m_fmt.width;}int ffmpeg_video_decoder_datasource::height(){ m_lock.enter(); _need_fmt_uptodate(); m_lock.leave(); return m_fmt.height;}int ffmpeg_video_decoder_datasource::frameduration(){ if(m_fmt.frameduration <=0) _need_fmt_uptodate(); /* frame rates > 100 fps are unlikely For mp4 H263 video, ffmpeg fills its time_base.den with 1000, resulting in frameduration == 1000 musec, which is wrong. ffplay gets the correct frame rate from its stream, only takes it from the codec if the stream doesn't have that information See: ffmpeg/libavformat/utils.c, function dump_format(). */ if(m_fmt.frameduration <= 9999) m_fmt.frameduration = 33000; return m_fmt.frameduration;}voidffmpeg_video_decoder_datasource::_need_fmt_uptodate(){ // Private method: no locking if (m_fmt.height == 0) { m_fmt.height = m_con->height; } if (m_fmt.width == 0) { m_fmt.width = m_con->width; } AM_DBG lib::logger::get_logger()->debug("ffmpeg_video_decoder_datasource::_need_fmt_uptodate(): frameduration = %lld", m_fmt. frameduration);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -