mirror of
https://github.com/wang-bin/QtAV.git
synced 2026-10-28 19:16:48 +08:00
TODO: if demuxer read the eof packet, then it will not get that again if seek again, why? miss resetting something?
754 lines
25 KiB
C++
754 lines
25 KiB
C++
/******************************************************************************
|
|
QtAV: Media play library based on Qt and FFmpeg
|
|
Copyright (C) 2012-2013 Wang Bin <wbsecg1@gmail.com>
|
|
|
|
* This file is part of QtAV
|
|
|
|
This library 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.
|
|
|
|
This library 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 this library; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
******************************************************************************/
|
|
|
|
#include <QtAV/AVClock.h>
|
|
#include <QtAV/AVDemuxer.h>
|
|
#include <QtAV/Packet.h>
|
|
#include <QtAV/QtAV_Compat.h>
|
|
#include <QtCore/QThread>
|
|
#include <QtCore/QCoreApplication>
|
|
|
|
namespace QtAV {
|
|
|
|
const qint64 kSeekInterval = 168; //ms
|
|
AVDemuxer::AVDemuxer(const QString& fileName, QObject *parent)
|
|
:QObject(parent),started_(false),eof(false),pkt(new Packet())
|
|
,ipts(0),stream_idx(-1),audio_stream(-2),video_stream(-2)
|
|
,subtitle_stream(-2),_is_input(true),format_context(0)
|
|
,a_codec_context(0),v_codec_context(0),_file_name(fileName),master_clock(0)
|
|
,__interrupt_status(0)
|
|
{
|
|
av_register_all();
|
|
avformat_network_init();
|
|
if (!_file_name.isEmpty())
|
|
loadFile(_file_name);
|
|
|
|
//default network timeout
|
|
__interrupt_timeout = QTAV_DEFAULT_NETWORK_TIMEOUT;
|
|
|
|
}
|
|
|
|
AVDemuxer::~AVDemuxer()
|
|
{
|
|
close();
|
|
if (pkt) {
|
|
delete pkt;
|
|
pkt = 0;
|
|
}
|
|
avformat_network_deinit();
|
|
}
|
|
|
|
/*
|
|
* metodo per interruzione loop ffmpeg
|
|
* @param void*obj: classe attuale
|
|
* @return
|
|
* >0 Interruzione loop di ffmpeg!
|
|
*/
|
|
int AVDemuxer::__interrupt_cb(void *obj){
|
|
int ret = 0;
|
|
AVDemuxer* demuxer;
|
|
//qApp->processEvents();
|
|
if (!obj){
|
|
qWarning("Passed Null object!");
|
|
return(-1);
|
|
}
|
|
demuxer = (AVDemuxer*)obj;
|
|
//qDebug("Timer:%lld, timeout:%lld\n",demuxer->__interrupt_timer.elapsed(), demuxer->__interrupt_timeout);
|
|
|
|
//check manual interruption
|
|
if (demuxer->__interrupt_status > 0) {
|
|
qDebug("User Interrupt: -> quit!");
|
|
ret = 1;//interrupt
|
|
} else if((demuxer->__interrupt_timer.isValid()) && (demuxer->__interrupt_timer.hasExpired(demuxer->__interrupt_timeout)) ) {
|
|
qDebug("Timeout expired: %lld/%lld -> quit!",demuxer->__interrupt_timer.elapsed(), demuxer->__interrupt_timeout);
|
|
//TODO: emit a signal
|
|
ret = 1;//interrupt
|
|
}
|
|
|
|
//qDebug(" END ret:%d\n",ret);
|
|
return(ret);
|
|
}//AVDemuxer::__interrupt_cb()
|
|
|
|
|
|
bool AVDemuxer::readFrame()
|
|
{
|
|
QMutexLocker lock(&mutex);
|
|
Q_UNUSED(lock);
|
|
AVPacket packet;
|
|
//start timeout timer and timeout
|
|
__interrupt_timer.start();
|
|
|
|
int ret = av_read_frame(format_context, &packet); //0: ok, <0: error/end
|
|
|
|
//invalidate the timer
|
|
__interrupt_timer.invalidate();
|
|
|
|
if (ret != 0) {
|
|
if (ret == AVERROR_EOF) { //end of file. FIXME: why no eof if replaying by seek(0)?
|
|
if (!eof) {
|
|
eof = true;
|
|
started_ = false;
|
|
pkt->markEnd();
|
|
qDebug("End of file. %s %d", __FUNCTION__, __LINE__);
|
|
emit finished();
|
|
return true;
|
|
}
|
|
//pkt->data = QByteArray(); //flush
|
|
//return true;
|
|
return false; //frames after eof are eof frames
|
|
} else if (ret == AVERROR_INVALIDDATA) {
|
|
qWarning("AVERROR_INVALIDDATA");
|
|
}
|
|
qWarning("[AVDemuxer] error: %s", av_err2str(ret));
|
|
return false;
|
|
}
|
|
|
|
stream_idx = packet.stream_index; //TODO: check index
|
|
//check whether the 1st frame is alreay got. emit only once
|
|
if (!started_ && v_codec_context && v_codec_context->frame_number == 0) {
|
|
started_ = true;
|
|
emit started();
|
|
} else if (!started_ && a_codec_context && a_codec_context->frame_number == 0) {
|
|
started_ = true;
|
|
emit started();
|
|
}
|
|
if (stream_idx != videoStream() && stream_idx != audioStream()) {
|
|
//qWarning("[AVDemuxer] unknown stream index: %d", stream_idx);
|
|
return false;
|
|
}
|
|
pkt->data = QByteArray((const char*)packet.data, packet.size);
|
|
pkt->duration = packet.duration;
|
|
//if (packet.dts == AV_NOPTS_VALUE && )
|
|
if (packet.dts != AV_NOPTS_VALUE) //has B-frames
|
|
pkt->pts = packet.dts;
|
|
else if (packet.pts != AV_NOPTS_VALUE)
|
|
pkt->pts = packet.pts;
|
|
else
|
|
pkt->pts = 0;
|
|
|
|
AVStream *stream = format_context->streams[stream_idx];
|
|
pkt->pts *= av_q2d(stream->time_base);
|
|
|
|
if (stream->codec->codec_type == AVMEDIA_TYPE_SUBTITLE
|
|
&& (packet.flags & AV_PKT_FLAG_KEY)
|
|
&& packet.convergence_duration != AV_NOPTS_VALUE)
|
|
pkt->duration = packet.convergence_duration * av_q2d(stream->time_base);
|
|
else if (packet.duration > 0)
|
|
pkt->duration = packet.duration * av_q2d(stream->time_base);
|
|
else
|
|
pkt->duration = 0;
|
|
//qDebug("AVPacket.pts=%f, duration=%f, dts=%lld", pkt->pts, pkt->duration, packet.dts);
|
|
|
|
av_free_packet(&packet); //important!
|
|
return true;
|
|
}
|
|
|
|
Packet* AVDemuxer::packet() const
|
|
{
|
|
return pkt;
|
|
}
|
|
|
|
int AVDemuxer::stream() const
|
|
{
|
|
return stream_idx;
|
|
}
|
|
|
|
bool AVDemuxer::atEnd() const
|
|
{
|
|
return eof;
|
|
}
|
|
|
|
bool AVDemuxer::close()
|
|
{
|
|
eof = false;
|
|
stream_idx = -1;
|
|
audio_stream = video_stream = subtitle_stream = -2;
|
|
__interrupt_status = 0;
|
|
if (a_codec_context) {
|
|
qDebug("closing a_codec_context");
|
|
avcodec_close(a_codec_context);
|
|
a_codec_context = 0;
|
|
}
|
|
if (v_codec_context) {
|
|
qDebug("closing v_codec_context");
|
|
avcodec_close(v_codec_context);
|
|
v_codec_context = 0;
|
|
}
|
|
//av_close_input_file(format_context); //deprecated
|
|
if (format_context) {
|
|
qDebug("closing format_context");
|
|
avformat_close_input(&format_context); //libavf > 53.10.0
|
|
format_context = 0;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void AVDemuxer::setClock(AVClock *c)
|
|
{
|
|
master_clock = c;
|
|
}
|
|
|
|
AVClock* AVDemuxer::clock() const
|
|
{
|
|
return master_clock;
|
|
}
|
|
|
|
//TODO: seek by byte
|
|
void AVDemuxer::seek(qreal q)
|
|
{
|
|
if ((!a_codec_context && !v_codec_context) || !format_context) {
|
|
qWarning("can not seek. context not ready: %p %p %p", a_codec_context, v_codec_context, format_context);
|
|
return;
|
|
}
|
|
if (seek_timer.isValid()) {
|
|
//why sometimes seek_timer.elapsed() < 0
|
|
if (!seek_timer.hasExpired(kSeekInterval)) {
|
|
qDebug("seek too frequent. ignore");
|
|
return;
|
|
}
|
|
seek_timer.restart();
|
|
} else {
|
|
seek_timer.start();
|
|
}
|
|
QMutexLocker lock(&mutex);
|
|
Q_UNUSED(lock);
|
|
q = qMax<qreal>(0.0, q);
|
|
if (q >= 1.0) {
|
|
qWarning("Invalid seek position %f/1.0", q);
|
|
return;
|
|
}
|
|
#if 0
|
|
//t: unit is s
|
|
qreal t = q;// * (double)format_context->duration; //
|
|
int ret = av_seek_frame(format_context, -1, (int64_t)(t*AV_TIME_BASE), t > pkt->pts ? 0 : AVSEEK_FLAG_BACKWARD);
|
|
qDebug("[AVDemuxer] seek to %f %f %lld / %lld", q, pkt->pts, (int64_t)(t*AV_TIME_BASE), duration());
|
|
#else
|
|
//t: unit is us (10^-6 s, AV_TIME_BASE)
|
|
int64_t t = int64_t(q*duration());///AV_TIME_BASE;
|
|
//TODO: pkt->pts may be 0, compute manually. Check wether exceed the length
|
|
if (t >= duration()) {
|
|
qWarning("Invailid seek position: %lld/%lld", t, duration());
|
|
return;
|
|
}
|
|
bool backward = t <= (int64_t)(pkt->pts*AV_TIME_BASE);
|
|
qDebug("[AVDemuxer] seek to %f %f %lld / %lld backward=%lld", q, pkt->pts, t, duration(), backward);
|
|
//AVSEEK_FLAG_BACKWARD has no effect? because we know the timestamp
|
|
int seek_flag = (backward ? 0 : AVSEEK_FLAG_BACKWARD); //AVSEEK_FLAG_ANY
|
|
int ret = av_seek_frame(format_context, -1, t, seek_flag);
|
|
#endif
|
|
if (ret < 0) {
|
|
qWarning("[AVDemuxer] seek error: %s", av_err2str(ret));
|
|
return;
|
|
}
|
|
//replay
|
|
if (q == 0) {
|
|
qDebug("************seek to 0. started = false");
|
|
started_ = false;
|
|
v_codec_context->frame_number = 0; //TODO: why frame_number not changed after seek?
|
|
}
|
|
if (master_clock) {
|
|
master_clock->updateValue(qreal(t)/qreal(AV_TIME_BASE));
|
|
master_clock->updateExternalClock(t/1000LL); //in msec. ignore usec part using t/1000
|
|
}
|
|
//calc pts
|
|
//use AVThread::flush() when reaching end
|
|
//if (videoCodecContext())
|
|
// avcodec_flush_buffers(videoCodecContext());
|
|
//if (audioCodecContext())
|
|
// avcodec_flush_buffers(audioCodecContext());
|
|
}
|
|
|
|
/*
|
|
TODO: seek by byte/frame
|
|
We need to know current playing packet but not current demuxed packet which
|
|
may blocked for a while
|
|
*/
|
|
void AVDemuxer::seekForward()
|
|
{
|
|
if ((!a_codec_context && !v_codec_context) || !format_context) {
|
|
qWarning("can not seek. context not ready: %p %p %p", a_codec_context, v_codec_context, format_context);
|
|
return;
|
|
}
|
|
double pts = pkt->pts;
|
|
if (master_clock) {
|
|
pts = master_clock->value();
|
|
} else {
|
|
qWarning("[AVDemux] No master clock!");
|
|
}
|
|
double q = (double)((pts + 16)*AV_TIME_BASE)/(double)duration();
|
|
seek(q);
|
|
}
|
|
|
|
void AVDemuxer::seekBackward()
|
|
{
|
|
if ((!a_codec_context && !v_codec_context) || !format_context) {
|
|
qWarning("can not seek. context not ready: %p %p %p", a_codec_context, v_codec_context, format_context);
|
|
return;
|
|
}
|
|
double pts = pkt->pts;
|
|
if (master_clock) {
|
|
pts = master_clock->value();
|
|
} else {
|
|
qWarning("[AVDemux] No master clock!");
|
|
}
|
|
double q = (double)((pts - 16)*AV_TIME_BASE)/(double)duration();
|
|
seek(q);
|
|
}
|
|
|
|
bool AVDemuxer::loadFile(const QString &fileName)
|
|
{
|
|
close();
|
|
qDebug("all closed and reseted");
|
|
_file_name = fileName;
|
|
//deprecated
|
|
// Open an input stream and read the header. The codecs are not opened.
|
|
//if(av_open_input_file(&format_context, _file_name.toLocal8Bit().constData(), NULL, 0, NULL)) {
|
|
|
|
//alloc av format context
|
|
if(!format_context)
|
|
format_context = avformat_alloc_context();
|
|
|
|
//install interrupt callback
|
|
format_context->interrupt_callback.callback = __interrupt_cb;
|
|
format_context->interrupt_callback.opaque = this;
|
|
|
|
qDebug("avformat_open_input: format_context:'%p', url:'%s'...",format_context, qPrintable(_file_name));
|
|
|
|
//start timeout timer and timeout
|
|
__interrupt_timer.start();
|
|
|
|
int ret = avformat_open_input(&format_context, qPrintable(_file_name), NULL, NULL);
|
|
|
|
//invalidate the timer
|
|
__interrupt_timer.invalidate();
|
|
|
|
qDebug("avformat_open_input: url:'%s' ret:%d",qPrintable(_file_name), ret);
|
|
|
|
if (ret < 0) {
|
|
//if (avformat_open_input(&format_context, qPrintable(filename), NULL, NULL)) {
|
|
qWarning("Can't open media: %s", av_err2str(ret));
|
|
return false;
|
|
}
|
|
format_context->flags |= AVFMT_FLAG_GENPTS;
|
|
//deprecated
|
|
//if(av_find_stream_info(format_context)<0) {
|
|
//TODO: avformat_find_stream_info is too slow, only useful for some video format
|
|
ret = avformat_find_stream_info(format_context, NULL);
|
|
if (ret < 0) {
|
|
qWarning("Can't find stream info: %s", av_err2str(ret));
|
|
return false;
|
|
}
|
|
|
|
//a_codec_context = format_context->streams[audioStream()]->codec;
|
|
//v_codec_context = format_context->streams[videoStream()]->codec;
|
|
findAVCodec();
|
|
|
|
bool _has_audio = a_codec_context != 0;
|
|
if (a_codec_context) {
|
|
AVCodec *aCodec = avcodec_find_decoder(a_codec_context->codec_id);
|
|
if (aCodec) {
|
|
ret = avcodec_open2(a_codec_context, aCodec, NULL);
|
|
if (ret < 0) {
|
|
_has_audio = false;
|
|
qWarning("open audio codec failed: %s", av_err2str(ret));
|
|
}
|
|
} else {
|
|
_has_audio = false;
|
|
qDebug("Unsupported audio codec. id=%d.", a_codec_context->codec_id);
|
|
}
|
|
}
|
|
if (master_clock->isClockAuto()) {
|
|
qDebug("auto select clock: audio > external");
|
|
if (!_has_audio) {
|
|
qWarning("No audio found or audio not supported. Using ExternalClock");
|
|
master_clock->setClockType(AVClock::ExternalClock);
|
|
} else {
|
|
qDebug("Using AudioClock");
|
|
master_clock->setClockType(AVClock::AudioClock);
|
|
}
|
|
}
|
|
|
|
bool _has_vedio = v_codec_context != 0;
|
|
if (v_codec_context) {
|
|
AVCodec *vCodec = avcodec_find_decoder(v_codec_context->codec_id);
|
|
if(!vCodec) {
|
|
qWarning("Unsupported video codec. id=%d.", v_codec_context->codec_id);
|
|
_has_vedio = false;
|
|
}
|
|
////v_codec_context->time_base = (AVRational){1,30};
|
|
//avcodec_open(v_codec_context, vCodec) //deprecated
|
|
ret = avcodec_open2(v_codec_context, vCodec, NULL);
|
|
if (ret < 0) {
|
|
qWarning("open video codec failed: %s", av_err2str(ret));
|
|
_has_vedio = false;
|
|
} else {
|
|
if (vCodec->capabilities & CODEC_CAP_DR1)
|
|
v_codec_context->flags |= CODEC_FLAG_EMU_EDGE;
|
|
}
|
|
//if (hurry_up) {
|
|
// codec_ctx->skip_frame = AVDISCARD_NONREF;
|
|
//codec_ctx->skip_loop_filter = codec_ctx->skip_idct = AVDISCARD_ALL;
|
|
//codec_ctx->flags2 |= CODEC_FLAG2_FAST;
|
|
//}
|
|
//else {
|
|
/*codec_ctx->skip_frame =*/ v_codec_context->skip_loop_filter = v_codec_context->skip_idct = AVDISCARD_DEFAULT;
|
|
v_codec_context->flags2 &= ~CODEC_FLAG2_FAST;
|
|
//}
|
|
bool skipframes = false;
|
|
v_codec_context->skip_frame = skipframes ? AVDISCARD_NONREF : AVDISCARD_DEFAULT;
|
|
}
|
|
started_ = false;
|
|
return _has_audio || _has_vedio;
|
|
}
|
|
|
|
AVFormatContext* AVDemuxer::formatContext()
|
|
{
|
|
return format_context;
|
|
}
|
|
/*
|
|
static void dump_stream_format(AVFormatContext *ic, int i, int index, int is_output)
|
|
{
|
|
char buf[256];
|
|
int flags = (is_output ? ic->oformat->flags : ic->iformat->flags);
|
|
AVStream *st = ic->streams[i];
|
|
int g = av_gcd(st->time_base.num, st->time_base.den);
|
|
AVDictionaryEntry *lang = av_dict_get(st->metadata, "language", NULL, 0);
|
|
avcodec_string(buf, sizeof(buf), st->codec, is_output);
|
|
av_log(NULL, AV_LOG_INFO, " Stream #%d.%d", index, i);
|
|
// the pid is an important information, so we display it
|
|
// XXX: add a generic system
|
|
if (flags & AVFMT_SHOW_IDS)
|
|
av_log(NULL, AV_LOG_INFO, "[0x%x]", st->id);
|
|
if (lang)
|
|
av_log(NULL, AV_LOG_INFO, "(%s)", lang->value);
|
|
av_log(NULL, AV_LOG_DEBUG, ", %d, %d/%d", st->codec_info_nb_frames, st->time_base.num/g, st->time_base.den/g);
|
|
av_log(NULL, AV_LOG_INFO, ": %s", buf);
|
|
if (st->sample_aspect_ratio.num && // default
|
|
av_cmp_q(st->sample_aspect_ratio, st->codec->sample_aspect_ratio)) {
|
|
AVRational display_aspect_ratio;
|
|
av_reduce(&display_aspect_ratio.num, &display_aspect_ratio.den,
|
|
st->codec->width*st->sample_aspect_ratio.num,
|
|
st->codec->height*st->sample_aspect_ratio.den,
|
|
1024*1024);
|
|
av_log(NULL, AV_LOG_INFO, ", PAR %d:%d DAR %d:%d",
|
|
st->sample_aspect_ratio.num, st->sample_aspect_ratio.den,
|
|
display_aspect_ratio.num, display_aspect_ratio.den);
|
|
}
|
|
if(st->codec->codec_type == AVMEDIA_TYPE_VIDEO){
|
|
if(st->avg_frame_rate.den && st->avg_frame_rate.num)
|
|
print_fps(av_q2d(st->avg_frame_rate), "fps");
|
|
if(st->r_frame_rate.den && st->r_frame_rate.num)
|
|
print_fps(av_q2d(st->r_frame_rate), "tbr");
|
|
if(st->time_base.den && st->time_base.num)
|
|
print_fps(1/av_q2d(st->time_base), "tbn");
|
|
if(st->codec->time_base.den && st->codec->time_base.num)
|
|
print_fps(1/av_q2d(st->codec->time_base), "tbc");
|
|
}
|
|
if (st->disposition & AV_DISPOSITION_DEFAULT)
|
|
av_log(NULL, AV_LOG_INFO, " (default)");
|
|
if (st->disposition & AV_DISPOSITION_DUB)
|
|
av_log(NULL, AV_LOG_INFO, " (dub)");
|
|
if (st->disposition & AV_DISPOSITION_ORIGINAL)
|
|
av_log(NULL, AV_LOG_INFO, " (original)");
|
|
if (st->disposition & AV_DISPOSITION_COMMENT)
|
|
av_log(NULL, AV_LOG_INFO, " (comment)");
|
|
if (st->disposition & AV_DISPOSITION_LYRICS)
|
|
av_log(NULL, AV_LOG_INFO, " (lyrics)");
|
|
if (st->disposition & AV_DISPOSITION_KARAOKE)
|
|
av_log(NULL, AV_LOG_INFO, " (karaoke)");
|
|
if (st->disposition & AV_DISPOSITION_FORCED)
|
|
av_log(NULL, AV_LOG_INFO, " (forced)");
|
|
if (st->disposition & AV_DISPOSITION_HEARING_IMPAIRED)
|
|
av_log(NULL, AV_LOG_INFO, " (hearing impaired)");
|
|
if (st->disposition & AV_DISPOSITION_VISUAL_IMPAIRED)
|
|
av_log(NULL, AV_LOG_INFO, " (visual impaired)");
|
|
if (st->disposition & AV_DISPOSITION_CLEAN_EFFECTS)
|
|
av_log(NULL, AV_LOG_INFO, " (clean effects)");
|
|
av_log(NULL, AV_LOG_INFO, "\n");
|
|
dump_metadata(NULL, st->metadata, " ");
|
|
}*/
|
|
void AVDemuxer::dump()
|
|
{
|
|
av_dump_format(format_context, 0, qPrintable(_file_name), false);
|
|
fflush(0);
|
|
qDebug("[AVFormatContext::duration = %lld]", duration());
|
|
qDebug("video format: %s [%s]", qPrintable(videoFormatName()), qPrintable(videoFormatLongName()));
|
|
qDebug("Audio: %s [%s]", qPrintable(audioCodecName()), qPrintable(audioCodecLongName()));
|
|
if (a_codec_context)
|
|
qDebug("sample rate: %d, channels: %d", a_codec_context->sample_rate, a_codec_context->channels);
|
|
struct stream_info {
|
|
int index;
|
|
AVCodecContext* ctx;
|
|
const char* name;
|
|
};
|
|
|
|
stream_info stream_infos[] = {
|
|
{audioStream(), a_codec_context, "audio stream"}
|
|
, {videoStream(), v_codec_context, "video_stream"}
|
|
, {0, 0, 0}
|
|
};
|
|
AVStream *stream = 0;
|
|
for (int idx = 0; stream_infos[idx].name != 0; ++idx) {
|
|
qDebug("%s: %d", stream_infos[idx].name, stream_infos[idx].index);
|
|
if (stream_infos[idx].index < 0 || !(stream = format_context->streams[stream_infos[idx].index])) {
|
|
qDebug("stream not available: index = %d, stream = %p", stream_infos[idx].index, stream);
|
|
continue;
|
|
}
|
|
qDebug("[AVStream::start_time = %lld]", stream->start_time);
|
|
AVCodecContext *ctx = stream_infos[idx].ctx;
|
|
if (ctx) {
|
|
qDebug("[AVCodecContext::time_base = %d / %d = %f]", ctx->time_base.num, ctx->time_base.den, av_q2d(ctx->time_base));
|
|
}
|
|
qDebug("[AVStream::avg_frame_rate = %d / %d = %f]", stream->avg_frame_rate.num, stream->avg_frame_rate.den, av_q2d(stream->avg_frame_rate));
|
|
qDebug("[AVStream::time_base = %d / %d = %f]", stream->time_base.num, stream->time_base.den, av_q2d(stream->time_base));
|
|
}
|
|
|
|
}
|
|
|
|
QString AVDemuxer::fileName() const
|
|
{
|
|
return format_context->filename;
|
|
}
|
|
|
|
QString AVDemuxer::videoFormatName() const
|
|
{
|
|
return formatName(format_context, false);
|
|
}
|
|
|
|
QString AVDemuxer::videoFormatLongName() const
|
|
{
|
|
return formatName(format_context, true);
|
|
}
|
|
|
|
qint64 AVDemuxer::startTime() const
|
|
{
|
|
return format_context->start_time;
|
|
}
|
|
|
|
//AVFrameContext use AV_TIME_BASE as time base.
|
|
qint64 AVDemuxer::duration() const
|
|
{
|
|
return format_context->duration; //time base: AV_TIME_BASE
|
|
}
|
|
|
|
int AVDemuxer::bitRate() const
|
|
{
|
|
return format_context->bit_rate;
|
|
}
|
|
|
|
qreal AVDemuxer::frameRate() const
|
|
{
|
|
AVStream *stream = format_context->streams[videoStream()];
|
|
return (qreal)stream->r_frame_rate.num / (qreal)stream->r_frame_rate.den;
|
|
//codecCtx->time_base.den / codecCtx->time_base.num
|
|
}
|
|
|
|
qint64 AVDemuxer::frames() const
|
|
{
|
|
return format_context->streams[videoStream()]->nb_frames; //0?
|
|
}
|
|
|
|
bool AVDemuxer::isInput() const
|
|
{
|
|
return _is_input;
|
|
}
|
|
|
|
int AVDemuxer::audioStream() const
|
|
{
|
|
if (audio_stream != -2) //-2: not parsed, -1 not found.
|
|
return audio_stream;
|
|
audio_stream = -1;
|
|
for(unsigned int i=0; i<format_context->nb_streams; ++i) {
|
|
if(format_context->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) {
|
|
audio_stream = i;
|
|
break;
|
|
}
|
|
}
|
|
return audio_stream;
|
|
}
|
|
|
|
int AVDemuxer::videoStream() const
|
|
{
|
|
if (video_stream != -2) //-2: not parsed, -1 not found.
|
|
return video_stream;
|
|
video_stream = -1;
|
|
for(unsigned int i=0; i<format_context->nb_streams; ++i) {
|
|
if(format_context->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
|
|
video_stream = i;
|
|
break;
|
|
}
|
|
}
|
|
return video_stream;
|
|
}
|
|
|
|
int AVDemuxer::subtitleStream() const
|
|
{
|
|
if (subtitle_stream != -2) //-2: not parsed, -1 not found.
|
|
return subtitle_stream;
|
|
subtitle_stream = -1;
|
|
for(unsigned int i=0; i<format_context->nb_streams; ++i) {
|
|
if(format_context->streams[i]->codec->codec_type == AVMEDIA_TYPE_SUBTITLE) {
|
|
subtitle_stream = i;
|
|
break;
|
|
}
|
|
}
|
|
return subtitle_stream;
|
|
}
|
|
|
|
int AVDemuxer::width() const
|
|
{
|
|
return videoCodecContext()->width;
|
|
}
|
|
|
|
int AVDemuxer::height() const
|
|
{
|
|
return videoCodecContext()->height;
|
|
}
|
|
|
|
QSize AVDemuxer::frameSize() const
|
|
{
|
|
return QSize(width(), height());
|
|
}
|
|
|
|
//codec
|
|
AVCodecContext* AVDemuxer::audioCodecContext() const
|
|
{
|
|
return a_codec_context;
|
|
}
|
|
|
|
AVCodecContext* AVDemuxer::videoCodecContext() const
|
|
{
|
|
return v_codec_context;
|
|
}
|
|
|
|
/*!
|
|
call avcodec_open2() first!
|
|
check null ptr?
|
|
*/
|
|
QString AVDemuxer::audioCodecName() const
|
|
{
|
|
if (a_codec_context)
|
|
return a_codec_context->codec->name;
|
|
//return v_codec_context->codec_name; //codec_name is empty? codec_id is correct
|
|
return "";
|
|
}
|
|
|
|
QString AVDemuxer::audioCodecLongName() const
|
|
{
|
|
if (a_codec_context)
|
|
return a_codec_context->codec->long_name;
|
|
return "";
|
|
}
|
|
|
|
QString AVDemuxer::videoCodecName() const
|
|
{
|
|
if (v_codec_context)
|
|
return v_codec_context->codec->name;
|
|
//return v_codec_context->codec_name; //codec_name is empty? codec_id is correct
|
|
return "";
|
|
}
|
|
|
|
QString AVDemuxer::videoCodecLongName() const
|
|
{
|
|
if (v_codec_context)
|
|
return v_codec_context->codec->long_name;
|
|
return "";
|
|
}
|
|
|
|
|
|
bool AVDemuxer::findAVCodec()
|
|
{
|
|
if (video_stream != -2 && audio_stream != -2 && subtitle_stream != -2)
|
|
return (video_stream != -1) && (audio_stream != -1) && (subtitle_stream != -1);
|
|
video_stream = audio_stream = subtitle_stream = -1;
|
|
AVMediaType type = AVMEDIA_TYPE_UNKNOWN;
|
|
for (unsigned int i=0; i<format_context->nb_streams; ++i) {
|
|
type = format_context->streams[i]->codec->codec_type;
|
|
if (type == AVMEDIA_TYPE_VIDEO && video_stream < 0) {
|
|
video_stream = i;
|
|
v_codec_context = format_context->streams[video_stream]->codec;
|
|
//if !vaapi
|
|
if (v_codec_context->codec_id == CODEC_ID_H264) {
|
|
v_codec_context->thread_type = FF_THREAD_FRAME; //FF_THREAD_SLICE;
|
|
v_codec_context->thread_count = QThread::idealThreadCount();
|
|
} else {
|
|
//v_codec_context->lowres = 0;
|
|
}
|
|
} else if (type == AVMEDIA_TYPE_AUDIO && audio_stream < 0) {
|
|
audio_stream = i;
|
|
a_codec_context = format_context->streams[audio_stream]->codec;
|
|
} else if (type == AVMEDIA_TYPE_SUBTITLE && subtitle_stream < 0) {
|
|
subtitle_stream = i;
|
|
}
|
|
if (audio_stream >=0 && video_stream >= 0 && subtitle_stream >= 0)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
QString AVDemuxer::formatName(AVFormatContext *ctx, bool longName) const
|
|
{
|
|
if (isInput())
|
|
return longName ? ctx->iformat->long_name : ctx->iformat->name;
|
|
else
|
|
return longName ? ctx->oformat->long_name : ctx->oformat->name;
|
|
}
|
|
|
|
|
|
/**
|
|
* @brief getInterruptTimeout return the interrupt timeout
|
|
* @return
|
|
*/
|
|
qint64 AVDemuxer::getInterruptTimeout() const
|
|
{
|
|
return __interrupt_timeout;
|
|
}
|
|
|
|
/**
|
|
* @brief setInterruptTimeout set the interrupt timeout
|
|
* @param timeout
|
|
* @return
|
|
*/
|
|
void AVDemuxer::setInterruptTimeout(qint64 timeout)
|
|
{
|
|
__interrupt_timeout = timeout;
|
|
}
|
|
|
|
/**
|
|
* @brief getInterruptStatus return the interrupt status
|
|
* @return
|
|
*/
|
|
int AVDemuxer::getInterruptStatus() const{
|
|
return(__interrupt_status);
|
|
}
|
|
|
|
/**
|
|
* @brief setInterruptStatus set the interrupt status
|
|
* @param interrupt
|
|
* @return
|
|
*/
|
|
int AVDemuxer::setInterruptStatus(int interrupt){
|
|
__interrupt_status = (interrupt>0) ? 1 : 0;
|
|
return(__interrupt_status);
|
|
}
|
|
|
|
} //namespace QtAV
|