FFmpeg C ++ декодирование в отдельном потоке - PullRequest
1 голос
/ 12 июня 2019

Я пытаюсь декодировать видео с помощью FFmpeg, преобразовать его в текстуру openGL и отобразить в движке cocos2dx.Мне удалось сделать это, и он отображает видео, как я хотел, теперь проблема в производительности.Я получаю обновление Sprite каждый кадр (игра имеет фиксированную скорость 60 кадров в секунду, видео - 30 кадров в секунду), поэтому то, что я сделал, я декодировал и преобразовывал кадры взаимозаменяемо, не очень хорошо работал, теперь у меня настроена отдельная нить, где я декодирую вбесконечный цикл while с sleep(), чтобы он не перегружал процессор / программу.В настоящее время я установил 2 кадровых буфера pbo и флаг bool, чтобы сообщить моему циклу потока ffmpeg о декодировании другого кадра, так как я не знаю, как вручную ждать, когда декодировать другой кадр.Я искал в Интернете для решения этой проблемы, но не удалось получить никаких ответов.

Я смотрел на это: Декодирование видео непосредственно в текстуру в отдельном потоке , но это не решило мою проблему, так как это было просто преобразование YUV в RGB внутри шейдеров opengl, которые у меня есть.еще не сделано, но в настоящее время не проблема.

Дополнительная информация, которая может оказаться полезной, заключается в том, что мне не нужно заканчивать поток до выхода из приложения, и я готов использовать любой формат видео, включая без потерь.

Хорошо, основной цикл декодированиявыглядит следующим образом:

//.. this is inside of a constructor / init
//adding thread to array in order to save the thread    
global::global_pending_futures.push_back(std::async(std::launch::async, [=] {
        while (true) {
            if (isPlaying) {
                this->decodeLoop();
            }
            else {
                std::this_thread::sleep_for(std::chrono::milliseconds(3));
            }
        }
    }));

Причина, по которой я использую bool, чтобы проверить, был ли использован кадр, заключается в том, что основная функция декодирования занимает около 5 мс, чтобы завершить отладку, а затем должна ждать около 11 мс, чтобы отобразить кадр,поэтому я не могу знать, когда был показан кадр, и я также не знаю, сколько времени заняло декодирование.

Функция декодирования:

void video::decodeLoop() { //this should loop in a separate thread
    frameData* buff = nullptr;
    if (buf1.needsRefill) {
    /// buf1.bufferLock.lock();
        buff = &buf1;
        buf1.needsRefill = false;
        firstBuff = true;
    }
    else if (buf2.needsRefill) {
        ///buf2.bufferLock.lock();
        buff = &buf2;
        buf2.needsRefill = false;
        firstBuff = false;
    }

    if (buff == nullptr) {
        std::this_thread::sleep_for(std::chrono::milliseconds(1));
        return;//error? //wait?
    }

    //pack pixel buffer?

    if (getNextFrame(buff)) {
        getCurrentRBGConvertedFrame(buff);
    }
    else {
        loopedTimes++;
        if (loopedTimes >= repeatTimes) {
            stop();
        }
        else {
            restartVideoPlay(&buf1);//restart both
            restartVideoPlay(&buf2);
            if (getNextFrame(buff)) {
                getCurrentRBGConvertedFrame(buff);
            }
        }
    }
/// buff->bufferLock.unlock();

    return;
}

Как вы можете сказать, я сначала проверил,буфер использовался с помощью bool needsRefill и затем декодировал другой кадр.

frameData struct:

    struct frameData {
        frameData() {};
        ~frameData() {};

        AVFrame* frame;
        AVPacket* pkt;
        unsigned char* pdata;
        bool needsRefill = true;
        std::string name = "";

        std::mutex bufferLock;

        ///unsigned int crrFrame
        GLuint pboid = 0;
    };

И это называется каждый кадр:

void video::actualDraw() { //meant for cocos implementation
    if (this->isVisible()) {
        if (this->getOpacity() > 0) {
            if (isPlaying) {
                if (loopedTimes >= repeatTimes) { //ignore -1 because comparing unsgined to signed
                    this->stop();
                }
            }

            if (isPlaying) {
                this->setVisible(true);

                if (!display) { //skip frame
                    ///this->getNextFrame();
                    display = true;
                }
                else if (display) {
                    display = false;
                    auto buff = this->getData();                    
                    width = this->getWidth();
                    height = this->getHeight();
                    if (buff) {
                        if (buff->pdata) {

                            glBindBuffer(GL_PIXEL_UNPACK_BUFFER, buff->pboid);
                            glBufferData(GL_PIXEL_UNPACK_BUFFER, 3 * (width*height), buff->pdata, GL_DYNAMIC_DRAW);


                            glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, 0);///buff->pdata);                            glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
                        }

                        buff->needsRefill = true;
                    }
                }
            }
            else { this->setVisible(false); }
        }
    }
}

Функция getData, чтобы указать, какой фреймбуфер использует

video::frameData* video::getData() {
    if (firstBuff) {
        if (buf1.needsRefill == false) {
            ///firstBuff = false;
            return &buf1;///.pdata;
        }
    }
    else { //if false
        if (buf2.needsRefill == false) {
            ///firstBuff = true;
            return &buf2;///.pdata;
        }
    }
    return nullptr;
}

Я не уверен, что еще включить, я вставил весь код в pastebin.video.cpp: https://pastebin.com/cWGT6APn video.h https://pastebin.com/DswAXwXV

Чтобы подвести итог проблемы:

Как правильно реализовать декодирование в отдельном потоке / как оптимизировать текущий код?

В настоящее время видео отстает, когда какой-то другой поток или основной поток становится тяжелым, а затем недостаточно быстро декодируется.

1 Ответ

0 голосов
/ 12 июня 2019

Вам нужно:

  • Два буфера, один может быть заполнен декодером, в то время как другой скопировал в ГПУ. Кроме того, используйте переменную (например, bool useFirst), чтобы сказать какой буфер используется для чтения, а какой для записи.
  • Рабочий поток, который декодирует кадр и заполняет буфер. Этот поток читает useFirst, чтобы указать, какой буфер заполнить. Не нужно защищать буфер мьютексом.
  • A std :: условие, которое заставляет поток ожидать нового кадра, поступающего из FFmpeg и для буфера, доступного для записи.
  • Таймер, который срабатывает каждые 1/60 секунды и выполняет функцию для передачи данных в графический процессор (если буфер уже заполнен), а затем обновляет useFirst и переменную условия.
  • Возможно, второй поток читает кадр из FFmpeg, но не декодирует его.

поток [ы] должен быть отделяемым (вместо присоединяемым ), чтобы они могли жить «вечно». Они должны проверить другой флаг / условие / уведомить, который заставляет их закончить и удалить.

Вы также можете использовать только один буфер с удвоенным или большим размером и менять местами разделы записи / чтения, как описано в Асинхронные передачи в буфере .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...