int FormatContext::avioInterruptCb(void *opaque)
{
    if (!opaque)
        return 0;
    FormatContext *ctx = static_cast<FormatContext*>(opaque);
    return ctx->avioInterruptCb();
}
Beispiel #2
0
void FFDemux::addFormatContext(QString url, const QString &param)
{
    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);
}
Beispiel #5
0
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();
    }
}
Beispiel #7
0
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 {};
}