int FormatContext::avioInterruptCb(void *opaque) { if (!opaque) return 0; FormatContext *ctx = static_cast<FormatContext*>(opaque); return ctx->avioInterruptCb(); }
void FFDemux::addFormatContext(QString url, const QString ¶m) { FormatContext *fmtCtx = new FormatContext(reconnectStreamed); { QMutexLocker mL(&mutex); formatContexts.append(fmtCtx); } if (!url.contains("://")) url.prepend("file://"); if (fmtCtx->open(url, param)) streams_info.append(fmtCtx->streamsInfo); else { { QMutexLocker mL(&mutex); formatContexts.erase(formatContexts.end() - 1); } delete fmtCtx; } }
FileProperties::FileProperties( const FormatContext& formatContext ) : _formatContext( &formatContext.getAVFormatContext() ) , _videoStreams() , _audioStreams() , _dataStreams() , _subtitleStreams() , _attachementStreams() , _unknownStreams() { if( _formatContext ) detail::fillMetadataDictionnary( _formatContext->metadata, _metadatas ); }
FileProperties::FileProperties(const FormatContext& formatContext) : _formatContext(&formatContext) , _avFormatContext(&formatContext.getAVFormatContext()) , _videoStreams() , _audioStreams() , _dataStreams() , _subtitleStreams() , _attachementStreams() , _unknownStreams() { if(_avFormatContext) detail::fillMetadataDictionnary(_avFormatContext->metadata, _metadatas); NoDisplayProgress progress; extractStreamProperties(progress, eAnalyseLevelHeader); }
int main(int argc, char **argv) { if (argc < 3) return 1; av::init(); av::setFFmpegLoggingLevel(AV_LOG_DEBUG); string uri {argv[1]}; string out {argv[2]}; error_code ec; // // INPUT // FormatContext ictx; ssize_t videoStream = -1; VideoDecoderContext vdec; Stream vst; int count = 0; ictx.openInput(uri, ec); if (ec) { cerr << "Can't open input\n"; return 1; } ictx.findStreamInfo(ec); if (ec) { cerr << "Can't find streams: " << ec << ", " << ec.message() << endl; return 1; } for (size_t i = 0; i < ictx.streamsCount(); ++i) { auto st = ictx.stream(i); if (st.mediaType() == AVMEDIA_TYPE_VIDEO) { videoStream = i; vst = st; break; } } if (vst.isNull()) { cerr << "Video stream not found\n"; return 1; } if (vst.isValid()) { vdec = VideoDecoderContext(vst); vdec.setRefCountedFrames(true); cerr << "PTR: " << (void*)vdec.raw()->codec << endl; vdec.open(Codec(), ec); if (ec) { cerr << "Can't open decoder\n"; return 1; } } // // OUTPUT // OutputFormat ofrmt; FormatContext octx; ofrmt.setFormat("flv", out); octx.setFormat(ofrmt); Codec ocodec = findEncodingCodec(ofrmt); Stream ost = octx.addStream(ocodec); VideoEncoderContext encoder {ost}; // Settings encoder.setWidth(vdec.width() * 2); encoder.setHeight(vdec.height() * 2); encoder.setPixelFormat(vdec.pixelFormat()); encoder.setTimeBase(Rational{1, 1000}); encoder.setBitRate(vdec.bitRate()); encoder.addFlags(octx.outputFormat().isFlags(AVFMT_GLOBALHEADER) ? CODEC_FLAG_GLOBAL_HEADER : 0); ost.setFrameRate(vst.frameRate()); ost.setTimeBase(encoder.timeBase()); octx.openOutput(out, ec); if (ec) { cerr << "Can't open output\n"; return 1; } encoder.open(ec); if (ec) { cerr << "Can't opent encoder\n"; return 1; } octx.dump(); octx.writeHeader(); octx.flush(); // // RESCALER // VideoRescaler rescaler; // Rescaler will be inited on demaind // // PROCESS // while (true) { // READING Packet pkt = ictx.readPacket(ec); if (ec) { clog << "Packet reading error: " << ec << ", " << ec.message() << endl; break; } // EOF if (!pkt) { break; } if (pkt.streamIndex() != videoStream) { continue; } clog << "Read packet: pts=" << pkt.pts() << ", dts=" << pkt.dts() << " / " << pkt.pts().seconds() << " / " << pkt.timeBase() << " / st: " << pkt.streamIndex() << endl; // DECODING auto inpFrame = vdec.decode(pkt, ec); count++; if (count > 200) break; if (ec) { cerr << "Decoding error: " << ec << endl; return 1; } else if (!inpFrame) { cerr << "Empty frame\n"; continue; } clog << "inpFrame: pts=" << inpFrame.pts() << " / " << inpFrame.pts().seconds() << " / " << inpFrame.timeBase() << ", " << inpFrame.width() << "x" << inpFrame.height() << ", size=" << inpFrame.size() << ", ref=" << inpFrame.isReferenced() << ":" << inpFrame.refCount() << " / type: " << inpFrame.pictureType() << endl; // Change timebase inpFrame.setTimeBase(encoder.timeBase()); inpFrame.setStreamIndex(0); inpFrame.setPictureType(); clog << "inpFrame: pts=" << inpFrame.pts() << " / " << inpFrame.pts().seconds() << " / " << inpFrame.timeBase() << ", " << inpFrame.width() << "x" << inpFrame.height() << ", size=" << inpFrame.size() << ", ref=" << inpFrame.isReferenced() << ":" << inpFrame.refCount() << " / type: " << inpFrame.pictureType() << endl; // SCALE //VideoFrame outFrame {encoder.pixelFormat(), encoder.width(), encoder.height()}; //rescaler.rescale(outFrame, inpFrame, ec); auto outFrame = rescaler.rescale(inpFrame, ec); if (ec) { cerr << "Can't rescale frame: " << ec << ", " << ec.message() << endl; return 1; } clog << "outFrame: pts=" << outFrame.pts() << " / " << outFrame.pts().seconds() << " / " << outFrame.timeBase() << ", " << outFrame.width() << "x" << outFrame.height() << ", size=" << outFrame.size() << ", ref=" << outFrame.isReferenced() << ":" << outFrame.refCount() << " / type: " << outFrame.pictureType() << endl; // ENCODE Packet opkt = encoder.encode(outFrame, ec); if (ec) { cerr << "Encoding error: " << ec << endl; return 1; } else if (!opkt) { cerr << "Empty packet\n"; continue; } // Only one output stream opkt.setStreamIndex(0); clog << "Write packet: pts=" << opkt.pts() << ", dts=" << opkt.dts() << " / " << opkt.pts().seconds() << " / " << opkt.timeBase() << " / st: " << opkt.streamIndex() << endl; octx.writePacket(opkt, ec); if (ec) { cerr << "Error write packet: " << ec << ", " << ec.message() << endl; return 1; } } octx.writeTrailer(); ictx.close(); }
int main(int argc, char **argv) { if (argc < 3) return 1; av::init(); av::setFFmpegLoggingLevel(AV_LOG_TRACE); string uri (argv[1]); string out (argv[2]); ssize_t audioStream = -1; AudioDecoderContext adec; Stream ast; error_code ec; int count = 0; { // // INPUT // FormatContext ictx; ictx.openInput(uri, ec); if (ec) { cerr << "Can't open input\n"; return 1; } ictx.findStreamInfo(); for (size_t i = 0; i < ictx.streamsCount(); ++i) { auto st = ictx.stream(i); if (st.isAudio()) { audioStream = i; ast = st; break; } } cerr << audioStream << endl; if (ast.isNull()) { cerr << "Audio stream not found\n"; return 1; } if (ast.isValid()) { adec = AudioDecoderContext(ast); //Codec codec = findDecodingCodec(adec.raw()->codec_id); //adec.setCodec(codec); //adec.setRefCountedFrames(true); adec.open(ec); if (ec) { cerr << "Can't open codec\n"; return 1; } } // // OUTPUT // OutputFormat ofmt; FormatContext octx; ofmt = av::guessOutputFormat(out, out); clog << "Output format: " << ofmt.name() << " / " << ofmt.longName() << '\n'; octx.setFormat(ofmt); Codec ocodec = av::findEncodingCodec(ofmt, false); Stream ost = octx.addStream(ocodec); AudioEncoderContext enc (ost); clog << ocodec.name() << " / " << ocodec.longName() << ", audio: " << (ocodec.type()==AVMEDIA_TYPE_AUDIO) << '\n'; auto sampleFmts = ocodec.supportedSampleFormats(); auto sampleRates = ocodec.supportedSamplerates(); auto layouts = ocodec.supportedChannelLayouts(); clog << "Supported sample formats:\n"; for (const auto &fmt : sampleFmts) { clog << " " << av_get_sample_fmt_name(fmt) << '\n'; } clog << "Supported sample rates:\n"; for (const auto &rate : sampleRates) { clog << " " << rate << '\n'; } clog << "Supported sample layouts:\n"; for (const auto &lay : layouts) { char buf[128] = {0}; av_get_channel_layout_string(buf, sizeof(buf), av_get_channel_layout_nb_channels(lay), lay); clog << " " << buf << '\n'; } //return 0; // Settings #if 1 enc.setSampleRate(48000); enc.setSampleFormat(sampleFmts[0]); // Layout //enc.setChannelLayout(adec.channelLayout()); enc.setChannelLayout(AV_CH_LAYOUT_STEREO); //enc.setChannelLayout(AV_CH_LAYOUT_MONO); enc.setTimeBase(Rational(1, enc.sampleRate())); enc.setBitRate(adec.bitRate()); #else enc.setSampleRate(adec.sampleRate()); enc.setSampleFormat(adec.sampleFormat()); enc.setChannelLayout(adec.channelLayout()); enc.setTimeBase(adec.timeBase()); enc.setBitRate(adec.bitRate()); #endif octx.openOutput(out, ec); if (ec) { cerr << "Can't open output\n"; return 1; } enc.open(ec); if (ec) { cerr << "Can't open encoder\n"; return 1; } clog << "Encoder frame size: " << enc.frameSize() << '\n'; octx.dump(); octx.writeHeader(); octx.flush(); // // RESAMPLER // AudioResampler resampler(enc.channelLayout(), enc.sampleRate(), enc.sampleFormat(), adec.channelLayout(), adec.sampleRate(), adec.sampleFormat()); // // PROCESS // while (true) { Packet pkt = ictx.readPacket(ec); if (ec) { clog << "Packet reading error: " << ec << ", " << ec.message() << endl; break; } if (pkt.streamIndex() != audioStream) { continue; } clog << "Read packet: isNull=" << (bool)!pkt << ", " << pkt.pts() << "(nopts:" << pkt.pts().isNoPts() << ")" << " / " << pkt.pts().seconds() << " / " << pkt.timeBase() << " / st: " << pkt.streamIndex() << endl; #if 0 if (pkt.pts() == av::NoPts && pkt.timeBase() == Rational()) { clog << "Skip invalid timestamp packet: data=" << (void*)pkt.data() << ", size=" << pkt.size() << ", flags=" << pkt.flags() << " (corrupt:" << (pkt.flags() & AV_PKT_FLAG_CORRUPT) << ";key:" << (pkt.flags() & AV_PKT_FLAG_KEY) << ")" << ", side_data=" << (void*)pkt.raw()->side_data << ", side_data_count=" << pkt.raw()->side_data_elems << endl; //continue; } #endif auto samples = adec.decode(pkt, ec); count++; //if (count > 200) // break; if (ec) { cerr << "Decode error: " << ec << ", " << ec.message() << endl; return 1; } else if (!samples) { cerr << "Empty samples set\n"; //if (!pkt) // decoder flushed here // break; //continue; } clog << " Samples [in]: " << samples.samplesCount() << ", ch: " << samples.channelsCount() << ", freq: " << samples.sampleRate() << ", name: " << samples.channelsLayoutString() << ", pts: " << samples.pts().seconds() << ", ref=" << samples.isReferenced() << ":" << samples.refCount() << endl; // Empty samples set should not be pushed to the resampler, but it is valid case for the // end of reading: during samples empty, some cached data can be stored at the resampler // internal buffer, so we should consume it. if (samples) { resampler.push(samples, ec); if (ec) { clog << "Resampler push error: " << ec << ", text: " << ec.message() << endl; continue; } } // Pop resampler data bool getAll = !samples; while (true) { AudioSamples ouSamples(enc.sampleFormat(), enc.frameSize(), enc.channelLayout(), enc.sampleRate()); // Resample: bool hasFrame = resampler.pop(ouSamples, getAll, ec); if (ec) { clog << "Resampling status: " << ec << ", text: " << ec.message() << endl; break; } else if (!hasFrame) { break; } else clog << " Samples [ou]: " << ouSamples.samplesCount() << ", ch: " << ouSamples.channelsCount() << ", freq: " << ouSamples.sampleRate() << ", name: " << ouSamples.channelsLayoutString() << ", pts: " << ouSamples.pts().seconds() << ", ref=" << ouSamples.isReferenced() << ":" << ouSamples.refCount() << endl; // ENCODE ouSamples.setStreamIndex(0); ouSamples.setTimeBase(enc.timeBase()); Packet opkt = enc.encode(ouSamples, ec); if (ec) { cerr << "Encoding error: " << ec << ", " << ec.message() << endl; return 1; } else if (!opkt) { //cerr << "Empty packet\n"; continue; } opkt.setStreamIndex(0); clog << "Write packet: pts=" << opkt.pts() << ", dts=" << opkt.dts() << " / " << opkt.pts().seconds() << " / " << opkt.timeBase() << " / st: " << opkt.streamIndex() << endl; octx.writePacket(opkt, ec); if (ec) { cerr << "Error write packet: " << ec << ", " << ec.message() << endl; return 1; } } // For the first packets samples can be empty: decoder caching if (!pkt && !samples) break; } // // Is resampler flushed? // cerr << "Delay: " << resampler.delay() << endl; // // Flush encoder queue // clog << "Flush encoder:\n"; while (true) { AudioSamples null(nullptr); Packet opkt = enc.encode(null, ec); if (ec || !opkt) break; opkt.setStreamIndex(0); clog << "Write packet: pts=" << opkt.pts() << ", dts=" << opkt.dts() << " / " << opkt.pts().seconds() << " / " << opkt.timeBase() << " / st: " << opkt.streamIndex() << endl; octx.writePacket(opkt, ec); if (ec) { cerr << "Error write packet: " << ec << ", " << ec.message() << endl; return 1; } } octx.flush(); octx.writeTrailer(); } }
Playlist::Entries FFDemux::fetchTracks(const QString &url, bool &ok) { if (url.contains("://{") || !url.startsWith("file://")) return {}; const auto createFmtCtx = [&] { FormatContext *fmtCtx = new FormatContext; { QMutexLocker mL(&mutex); formatContexts.append(fmtCtx); } return fmtCtx; }; const auto destroyFmtCtx = [&](FormatContext *fmtCtx) { { QMutexLocker mL(&mutex); const int idx = formatContexts.indexOf(fmtCtx); if (idx > -1) formatContexts.remove(idx); } delete fmtCtx; }; if (url.endsWith(".cue", Qt::CaseInsensitive)) { QFile cue(url.mid(7)); if (cue.size() <= 0xFFFF && cue.open(QFile::ReadOnly | QFile::Text)) { QList<QByteArray> data = Functions::textWithFallbackEncoding(cue.readAll()).split('\n'); QString title, performer, audioUrl; double index0 = -1.0, index1 = -1.0; QHash<int, QPair<double, double>> indexes; Playlist::Entries entries; Playlist::Entry entry; int track = -1; const auto maybeFlushTrack = [&](int prevTrack) { if (track <= 0) return; const auto cutFromQuotation = [](QString &str) { const int idx1 = str.indexOf('"'); const int idx2 = str.lastIndexOf('"'); if (idx1 > -1 && idx2 > idx1) str = str.mid(idx1 + 1, idx2 - idx1 - 1); else str.clear(); }; cutFromQuotation(title); cutFromQuotation(performer); if (!title.isEmpty() && !performer.isEmpty()) entry.name = performer + " - " + title; else if (title.isEmpty() && !performer.isEmpty()) entry.name = performer; else if (!title.isEmpty() && performer.isEmpty()) entry.name = title; if (entry.name.isEmpty()) { if (entry.parent == 1) entry.name = tr("Track") + " " + QString::number(prevTrack); else entry.name = Functions::fileName(entry.url, false); } title.clear(); performer.clear(); if (prevTrack > 0) { if (index0 <= 0.0) index0 = index1; // "INDEX 00" doesn't exist, use "INDEX 01" indexes[prevTrack].first = index0; indexes[prevTrack].second = index1; if (entries.count() <= prevTrack) entries.resize(prevTrack + 1); entries[prevTrack] = entry; } else { entries.prepend(entry); } entry = Playlist::Entry(); entry.parent = 1; entry.url = audioUrl; index0 = index1 = -1.0; }; const auto parseTime = [](const QByteArray &time) { int m = 0, s = 0, f = 0; if (sscanf(time.constData(), "%2d:%2d:%2d", &m, &s, &f) == 3) return m * 60.0 + s + f / 75.0; return -1.0; }; entry.url = url; entry.GID = 1; for (QByteArray &line : data) { line = line.trimmed(); if (track < 0) { if (line.startsWith("TITLE ")) title = line; else if (line.startsWith("PERFORMER ")) performer = line; else if (line.startsWith("FILE ")) { const int idx = line.lastIndexOf('"'); if (idx > -1) { audioUrl = line.mid(6, idx - 6); if (!audioUrl.isEmpty()) audioUrl.prepend(Functions::filePath(url)); } } } else if (line.startsWith("FILE ")) { // QMPlay2 supports CUE files which uses only single audio file return {}; } if (line.startsWith("TRACK ")) { if (entries.isEmpty() && audioUrl.isEmpty()) break; if (line.endsWith(" AUDIO")) { const int prevTrack = track; track = line.mid(6, 2).toInt(); if (track < 99) maybeFlushTrack(prevTrack); else track = 0; } else { track = 0; } } else if (track > 0) { if (line.startsWith("TITLE ")) title = line; else if (line.startsWith("PERFORMER ")) performer = line; else if (line.startsWith("INDEX 00 ")) index0 = parseTime(line.mid(9)); else if (line.startsWith("INDEX 01 ")) index1 = parseTime(line.mid(9)); } } maybeFlushTrack(track); for (int i = entries.count() - 1; i >= 1; --i) { Playlist::Entry &entry = entries[i]; const bool lastItem = (i == entries.count() - 1); const double start = indexes.value(i).second; const double end = indexes.value(i + 1, {-1.0, -1.0}).first; if (entry.url.isEmpty() || start < 0.0 || (end <= 0.0 && !lastItem)) { entries.removeAt(i); continue; } const QString param = QString("CUE:%1:%2").arg(start).arg(end); if (lastItem && end < 0.0) // Last entry doesn't have specified length in CUE file { FormatContext *fmtCtx = createFmtCtx(); if (fmtCtx->open(entry.url, param)) entry.length = fmtCtx->length(); destroyFmtCtx(fmtCtx); if (abortFetchTracks) { ok = false; return {}; } } else { entry.length = end - start; } entry.url = QString("%1://{%2}%3").arg(DemuxerName, entry.url, param); } if (!entries.isEmpty()) return entries; } } OggHelper oggHelper(url.mid(7), abortFetchTracks); if (oggHelper.io) { Playlist::Entries entries; int i = 0; for (const OggHelper::Chain &chains : oggHelper.getOggChains(ok)) { const QString param = QString("OGG:%1:%2:%3").arg(++i).arg(chains.first).arg(chains.second); FormatContext *fmtCtx = createFmtCtx(); if (fmtCtx->open(url, param)) { Playlist::Entry entry; entry.url = QString("%1://{%2}%3").arg(DemuxerName, url, param); entry.name = fmtCtx->title(); entry.length = fmtCtx->length(); entries.append(entry); } destroyFmtCtx(fmtCtx); if (abortFetchTracks) { ok = false; return {}; } } if (ok && !entries.isEmpty()) { for (int i = 0; i < entries.count(); ++i) entries[i].parent = 1; Playlist::Entry entry; entry.name = Functions::fileName(url, false); entry.url = url; entry.GID = 1; entries.prepend(entry); return entries; } } return {}; }