Форум программистов, компьютерный форум, киберфорум
OpenGL
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  
 
 
Рейтинг 4.75/16: Рейтинг темы: голосов - 16, средняя оценка - 4.75
Модератор
Эксперт Java
 Аватар для alecss131
2835 / 1344 / 403
Регистрация: 11.08.2017
Сообщений: 4,297
Записей в блоге: 2

Воспроизведение видео

15.02.2021, 22:46. Показов 4053. Ответов 34

Студворк — интернет-сервис помощи студентам
Давно уже были мысли создать тему про это, но только сейчас дошли руки. Собственно сам вопрос, как правильно воспроизводить видео с использованием opengl? Знаю про pbo, но с ним надо мапить буфер а сам этот процесс довольно затратный. Пока сделал тупо в лоб, рисую прямоугольник с текстурой на весь экран, две текстуры, одну рисую, другую заполняю через glTextureSubImage2D декодированными данными в том же кадре.
На fullhd видео у меня выдает в среднем 60 фпс (+- 1 крайне редко 2), в 4к все похуже, в среднем около 22 (+- 5) фпс
Форматы и размер видео не влияют на фпс, по крайней мере я не заметил. Видео декодирую через ffmeg (думаю в идеале нужно остановиться на одном формате, а то +15мб к весу как-то не очень, другой вопрос какой формат, но сейчас не об этом). Так как движок игровой куча форматов не нужны и буду видео подгонять к нужному фреймрейту, а то сейчас у меня всегда стремится к 60 не взирая на фпс самого видео)) Звук не реализовывал, будет думаю отдельно и через openal
Есть у кого опыт в данном вопросе? Точно ли будет выигрыш от pbo и стоит ли его использовать? В сети не нахожу понятных примеров использования pbo, особенно с dsa. И везде советуют заполнять буфер после мэппинга в другом потоке, а с потоками я совсем не разбираюсь. Знаю что нельзя использовать один контекст в разных потоках и можно расшаривать ресурсы, не проблема, в glfw создать еще одно окно как дочернее от основного и ресурсы будут общими. Думаю для меня важнее неблокируемый запись данных в текстуру, но можно ли это сделать в одном потоке. Ведь декодирование и конвертирование видео из YUV в RGBA тоже процесс не быстрый и блокирующий основной поток.
В данный момент так рисую
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
    while (!wnd.windowShouldClose()) {
        glClear(GL_COLOR_BUFFER_BIT);
        glBindTextureUnit(0, tex[n]);
        pipeline.bind();
        glBindVertexArray(vao);
        glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
        glBindVertexArray(0);
        pipeline.unbind();
        glBindTextureUnit(0, 0);
        n ^= 1;
        vd.process(tex[n]); //заполнение второй текстуры из видео
        wnd.update();
        
    }
Приложу весь проект (кода прилично, копировать сюда не влезет, а заливать на гит не хочу), использую galogen (opengl), glfw, ffmpeg и nativefiledialog (nfd для окна выбора файла для открытия)
OpenGL_VideoPlayer.7z
0
IT_Exp
Эксперт
34794 / 4073 / 2104
Регистрация: 17.06.2006
Сообщений: 32,602
Блог
15.02.2021, 22:46
Ответы с готовыми решениями:

Воспроизведение видео-файлов
Воспроизведение ВИДЕО файлов в SFML ктото пробовал ? Я только такую библиотеку Motion нашел и печально что примеров нет на ней :-!

DirectShow воспроизведение видео из буфера в ОЗУ
Подскажите, как на DirectShow воспроизвести файл (а еще лучше - произвольную часть файла), который предварительно считан в буфер в ОЗУ?...

Воспроизведение видео .h264 без сторонних кодеков
Всем привет! Делаю воспроизведение стартового видео в игре через DirectShow, столкнулся с проблемой. Если в системе установлены кодеки...

34
Модератор
Эксперт Java
 Аватар для alecss131
2835 / 1344 / 403
Регистрация: 11.08.2017
Сообщений: 4,297
Записей в блоге: 2
16.02.2021, 11:42  [ТС]
Немного поменял код, добавил pbo (надеюсь правильно), работать работает, но вот фпс не поменялся...
Было
C++
1
2
3
4
5
6
7
8
9
10
//в конструкторе 
dframe.data[0] = new unsigned char[s] {};
 
//в деструкторе
delete[] dframe.data[0];
 
//в методе process
sws_scale(imgConvertContext, frame->data, frame->linesize, 0,
    codecContext->height, dframe.data, dframe.linesize);
glTextureSubImage2D(tex, 0, 0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, dframe.data[0]);
Стало
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//в конструкторе 
glCreateBuffers(1, &pbo);
glNamedBufferStorage(pbo, s, nullptr, GL_DYNAMIC_STORAGE_BIT | GL_MAP_WRITE_BIT);
 
//в деструкторе
glDeleteBuffers(1, &pbo);
 
//в методе process
dframe.data[0] = (GLubyte*)glMapNamedBuffer(pbo, GL_WRITE_ONLY);
sws_scale(imgConvertContext, frame->data, frame->linesize, 0,
    codecContext->height, dframe.data, dframe.linesize);
glUnmapNamedBuffer(pbo);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
glTextureSubImage2D(tex, 0, 0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, 0);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
А так же сделал всего 1 текстуру, на производительности не сказалось, думаю если переходить на многопоток, то нужны будут не 2 текстуры, а 2 pbo
Только теперь начал плеваться нотификациями
Code
1
2
3
4
5
//один раз сначала
API:OTHER[NOTIFICATION](131185): Buffer detailed info: Buffer object 1 (bound to NONE, usage hint is GL_DYNAMIC_DRAW) will use SYSTEM HEAP memory as the source for buffer object operations.
//каждый кадр
API:OTHER[NOTIFICATION](131185): Buffer detailed info: Buffer object 1 (bound to NONE, usage hint is GL_DYNAMIC_DRAW) has been mapped WRITE_ONLY in SYSTEM HEAP memory (fast).
API:PERFORMANCE[MEDIUM](131154): Pixel-path performance warning: Pixel transfer is synchronized with 3D rendering.
Переводить в отдельный поток не хочется
0
 Аватар для zayats80888
6352 / 3523 / 1428
Регистрация: 07.02.2019
Сообщений: 8,995
16.02.2021, 17:29
alecss131, для начала, вместо glMapNamedBuffer используй glMapBufferRange с флагами GL_MAP_WRITE_BIT и GL_MAP_INVALIDATE_BUFFER_BIT.

А вообще это делается с помощью техники стриминга.
Создаёшь буфер, размером на N кадров, кадр размером frameSize(только размер данных одного кадра следует округлить в большую сторону, кратное выравниванию, которое подобрать опытным путём, что бы давало лучшую производительность, но минимальное возьми не меньше 4, дальше степени двойки, т.е. 8, 16, 32, 64 ...). Условно устанавливаешь указатель(offset) на начало буфера (0). Мэпишь диапазон offset + frameSize (с флагами GL_MAP_WRITE_BIT, GL_MAP_UNSYNCHRONIZED_BIT и GL_MAP_INVALIDATE_RANGE_BIT), загружаешь туда данные, анмэпишь, данный участок буфера можно использовать для последующего трансфера пикселей в текстуру. Сам указатель инкрементируешь(offset = offset + frameSize) и повторяешь загрузку, только в следующий участок. Когда дойдёшь до конца буфера, то ставишь указатель на начало(0), только мэпишь первый участок с инвалидацией всего буфера(GL_MAP_INVALIDATE_BUFFER_BIT, это аллоцирует новое хранилище), и продолжаешь как обычно. В итоге, пока ты загружаешь очередной кадр, предыдущий может использоваться GL. Получается асинхронно.
Вместо реаллокации всего буфера, когда он заполнен(что может быть затратно по памати), можно каждому из N участков сопоставить объект синхронизации (glFenceSync), что бы узнать, что данные этого участка уже были загружены в текстуру. Тогда после заполнения и перехода указателя на первый участок, перед тем как его мэпить, ждёшь объект синхронизации. Если памяти много, и можешь позволить буфер под много кадров, то скорее всего ожидания объекта синхронизации не будет, т.к. он уже был использован N кадров назад.
Так же один буфер можно заменить на несколько.
Данный подход асинхронен, хотя тут размен - память vs скорость.
1
Модератор
Эксперт Java
 Аватар для alecss131
2835 / 1344 / 403
Регистрация: 11.08.2017
Сообщений: 4,297
Записей в блоге: 2
16.02.2021, 21:13  [ТС]
zayats80888, спасибо, но возникли новые вопросы на основе вашего ответа))
сначала небольшое исправление, не glMapBufferRange а glMapNamedBufferRange ведь использую dsa и зачем лишние бинды)

теперь пару слов про то как это работает:
с помощью ffmpeg читаю из видеопотока кадр (frame), который представляет из себя три однобайтных чб картинки по одной на канал (вроде идущие в памяти последовательно) размером с видео
потом с помощью функции sws_scale (из той же библиотеки) преобразую эти 3 картинки в нужный RGBA8 формат и масштабирую к нужному размеру, в итоге получается новый кадр (dframe) который и использую
надеюсь узкое место не здесь и не будет здесь, ведь в случае с 4к видео у меня frame 4к, а dframe уже fullhd. откуда возникает первый вопрос, где лучше менять размер, масштабировать dframe или оставлять исходный размер и использовать эту текстуру в opengl для отрисовки более маленького прямоугольника? в любом случае будет смена формата, только с или без изменения размера
хоть у меня монитор и не 4к, но на компе попались видео этого размера, fullhd кадр в rgba8 весит почти 8мб, а 4к в том же формате почти 32мб, в планах делать разрешение рендера больше fullhd нету и в этом разрешении любой способ дает у меня стабильные 60 фпс (интересно проверить на других компах это)
теперь следующий вопрос, зачем надо делать invalidate буферу? ведь его же просто перезаписываем новыми данными, на что это влияет?
важен ли порядок действий? я про обновление текстуры из прошлого шага, заполнение буфера и отрисовку кадра, как понимаю заполнение буфера с его полной инвалидацией должно быть перед обновлением текстуры, а если частично то не понимаю
выравнивание для меня не очень приятная вещь, еще помню намучился с ним когда использовал glBindBufferRange, там нужно конкретное выравнивание было, а тут размер кадра почти 8 мб
еще не понимаю откуда тут асинхронность, ведь все в одном потоке и вызовы к видеокарте будут тормозиться обработкой видеопотока. за один шаг главного цикла по одному действию каждого вида, а то боюсь подготовка новых кадров видео тут самое затратное и долгое и можно не уложиться в 60 фпс, значит буфер более чем на 2 кадра не очень актуален
синхронизация для меня вообще темный лес, хотя и полезно было бы разобраться, ведь хочу потом освоить вулкан а там без ручной синхронизации не обойтись
у меня сейчас цель просто получить стабильные 60 фпс при проигрывании видео из 4к потока, это в идеале, план минимум fullhd поток, что уже есть, но надо улучшить))
по хорошему надо еще заняться синхронизацией фпс видео и фпс программы, но уж лучше к движку рендерить видео с нужным фпс, ведь я не видеоплеер пишу, а так же звуком, но звук сейчас последнее что интересно)))
0
с++
1282 / 523 / 225
Регистрация: 15.07.2015
Сообщений: 2,562
16.02.2021, 21:22
а почему нельзя использовать квадрат и на нем отображать видео, зачем все то что вы хотите использовать?
0
Модератор
Эксперт Java
 Аватар для alecss131
2835 / 1344 / 403
Регистрация: 11.08.2017
Сообщений: 4,297
Записей в блоге: 2
16.02.2021, 21:29  [ТС]
Цитата Сообщение от Antikl Посмотреть сообщение
а почему нельзя использовать квадрат и на нем отображать видео, зачем все то что вы хотите использовать?
можно уточнить как именно?
я в итоге и рисую прямоугольник размером с экран на который вывожу видео через текстуры. видео декодирую сам используя ffmpeg
0
с++
1282 / 523 / 225
Регистрация: 15.07.2015
Сообщений: 2,562
16.02.2021, 21:42
Цитата Сообщение от alecss131 Посмотреть сообщение
можно уточнить как именно?
попробуй отдельно

https://gist.github.com/rcolinray/7552384
0
 Аватар для zayats80888
6352 / 3523 / 1428
Регистрация: 07.02.2019
Сообщений: 8,995
16.02.2021, 21:50
Цитата Сообщение от alecss131 Посмотреть сообщение
теперь следующий вопрос, зачем надо делать invalidate буферу? ведь его же просто перезаписываем новыми данными, на что это влияет?
Если ты про GL_MAP_INVALIDATE_BUFFER_BIT, то это говорит GL, что предыдущее хранилище клиенту бульше не нужно, поэтому для этого объекта будет выделено новое. Старое клиенту не доступно, хотя все ожидающие(ранее отправленные, но фактически не выполненые) команды нормально завершатся с использованием старого хранилища(оно как бы доступно серверу, после чего отправится менеджеру памяти, как свободное и, скорее всего, при следующей инвалидации снова "приклеется" к буферу).
Если ты про GL_MAP_INVALIDATE_RANGE_BIT, то это подсказка по оптимизации, мы обещаем переписать все данные диапазона, старые нам не нужны, а значит можно их не загружать.

Цитата Сообщение от alecss131 Посмотреть сообщение
еще не понимаю откуда тут асинхронность,
Асинзронность в том, что твой обычный MapBuffer... вынужден ждать, пока все команды с буфером завершаться(если что, то swapbuffer, т.е. очередная итерация цикла рисования, не гарантирует, что всё уже завершено, драйвер это оптимизирует и может быть кэш на несколько кадров вперёд, особенно на твоей видюхе). В то время как мы можем мэпить незадействованный участок, то и синхронизация для него не нужна. Если у тебя производительность упирается в подготовку растра и твой обычный MapBuffer... не блокирует поток, то эти танцы не нужны(пока не решишь перенести это в отдельный поток/потоки, и рендерить с задержкой/буферизацией)
Цитата Сообщение от alecss131 Посмотреть сообщение
у меня сейчас цель просто получить стабильные 60 фпс при проигрывании видео из 4к потока
Ну так определи сначала узкое место. Я твой проект сейчас посмотреть не могу(неделю назад переехал на линукс, пока разбираюсь , не до этого сейчас).
0
Модератор
Эксперт Java
 Аватар для alecss131
2835 / 1344 / 403
Регистрация: 11.08.2017
Сообщений: 4,297
Записей в блоге: 2
16.02.2021, 22:32  [ТС]
Antikl, там по сути мой первый вариант и есть, каждый кадр заполнять текстуру через glTexSubImage2D который у меня для 4к 60 фпс видео дает в лучшем случае 27 фпс и в среднем 22 фпс
тем более тот код написан с использованием deprecated функций ffmpeg и не собирается, я руками переводил свой код на свежую версию

Добавлено через 36 минут
Сделал код с буфером удвоенного размера и биндом по частям, ничего не поменялось. Единственное отличие только в том если открывать файл fullhd то спамит нотификациями каждый кадр, а если 4к, то выводит всего по 1 разу и все
Размер буфера и так кратный 4, так как 4 канала в картинке
Вот код декодера
заголовок
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#ifndef _VIDEO_DECODER_HEADER_
#define _VIDEO_DECODER_HEADER_
#include <gl46.h>
#include <string>
extern "C" {
    #include <libavcodec/avcodec.h>
    #include <libavformat/avformat.h>
    #include <libswscale/swscale.h>
}
#ifndef NDEBUG
#include <iostream>
#endif
 
class VideoDecoder {
public :
    VideoDecoder(std::string fname, int width, int height);
    ~VideoDecoder();
    void process(GLuint tex);
private :
    void fillBuffer();
    void init(std::string fname);
    AVFormatContext* formatContext;
    int err;
    int videoStream;
    AVCodecContext* codecContext;
    AVCodec* codec;
    SwsContext* imgConvertContext;
    AVFrame* frame;
    AVPacket packet;
    AVFrame dframe;
    int w;
    int h;
    int s;
    GLuint pbo;
    int n;
};
#endif
реализация
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
#include "VideoDecoder.h"
 
VideoDecoder::VideoDecoder(std::string fname, int width, int height)
    : formatContext{}, err{}, videoStream{}, codecContext{}, codec{},
    imgConvertContext{}, frame{}, packet{}, dframe{}, w{ width }, h{height},
    s{}, pbo{} {
    init(fname);
    glCreateBuffers(1, &pbo);
    int off{ s * 2 };
    glNamedBufferStorage(pbo, off, nullptr, GL_DYNAMIC_STORAGE_BIT | GL_MAP_WRITE_BIT);
    fillBuffer();
}
 
VideoDecoder::~VideoDecoder() {
    glDeleteBuffers(1, &pbo);
    avformat_close_input(&formatContext);
    avcodec_free_context(&codecContext);
    
}
 
void VideoDecoder::process(GLuint tex) {
    glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
    int off{ n * s };
    glTextureSubImage2D(tex, 0, 0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, (void *)off);
    glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
    n ^= 1;
    fillBuffer();
}
 
void VideoDecoder::fillBuffer() {
    while (av_read_frame(formatContext, &packet) >= 0) {
        if (packet.stream_index == videoStream) {
            avcodec_send_packet(codecContext, &packet);
            if (avcodec_receive_frame(codecContext, frame) == 0) {
                int off{ n * s };
                dframe.data[0] = (GLubyte*)glMapNamedBufferRange(pbo, off, s, GL_MAP_WRITE_BIT 
                    | GL_MAP_INVALIDATE_RANGE_BIT | GL_MAP_UNSYNCHRONIZED_BIT);
                sws_scale(imgConvertContext, frame->data, frame->linesize, 0,
                    codecContext->height, dframe.data, dframe.linesize);
                glUnmapNamedBuffer(pbo);
            }
            break;
        }
    }
}
 
void VideoDecoder::init(std::string fname) {
    err = avformat_open_input(&formatContext, fname.c_str(), nullptr, nullptr);
#ifndef NDEBUG
    if (err < 0) {
        std::cerr << "ffmpeg: Unable to open input file" << std::endl;
        exit(-1);
    }
#endif
    err = avformat_find_stream_info(formatContext, nullptr);
#ifndef NDEBUG
    if (err < 0) {
        std::cerr << "ffmpeg: Unable to find stream info" << std::endl;;
        exit(-1);
    }
#endif
    for (unsigned int i = 0; i < formatContext->nb_streams; i++) {
        if (formatContext->streams[videoStream]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
            videoStream = i;
            break;
        }
    }
#ifndef NDEBUG
    if (videoStream == formatContext->nb_streams) {
        std::cerr << "ffmpeg: Unable to find video stream" << std::endl;
        exit(-1);
    }
#endif
    codec = avcodec_find_decoder(formatContext->streams[videoStream]->codecpar->codec_id);
    codecContext = avcodec_alloc_context3(codec);
    avcodec_parameters_to_context(codecContext, formatContext->streams[videoStream]->codecpar);
    err = avcodec_open2(codecContext, codec, nullptr);
#ifndef NDEBUG
    if (err < 0) {
        std::cerr << "ffmpeg: Unable to open codec" << std::endl;
        exit(-1);
    }
#endif
    imgConvertContext = sws_getCachedContext(nullptr,
        codecContext->width, codecContext->height, codecContext->pix_fmt,
        w, h, AV_PIX_FMT_RGBA, SWS_BICUBIC, nullptr, nullptr, nullptr);
#ifndef NDEBUG
    if (imgConvertContext == nullptr) {
        std::cerr << "Cannot initialize the conversion context" << std::endl;
        exit(-1);
    }
#endif
    frame = av_frame_alloc();
    int sz = w * 4;
    s = sz * h;
    dframe.linesize[0] = sz;
    dframe.channels = 4;
    dframe.width = w;
    dframe.height = h;
}
Добавлено через 3 минуты
zayats80888, Я флаги там где нужно указал? Пока без инвалидации всего буфера, только частями.
Ведь маплю буфер с флагом GL_MAP_UNSYNCHRONIZED_BIT а в нотификациях про синхронизацию, если этот флаг поместить в 10 строчку то не создается буфер
0
 Аватар для zayats80888
6352 / 3523 / 1428
Регистрация: 07.02.2019
Сообщений: 8,995
16.02.2021, 23:00
Цитата Сообщение от alecss131 Посмотреть сообщение
glNamedBufferStorage(pbo, off, nullptr, GL_DYNAMIC_STORAGE_BIT | GL_MAP_WRITE_BIT);
1) У тебя тут off равен нулю.
2) Флаги: GL_MAP_WRITE_BIT (и, возможно, GL_CLIENT_STORAGE_BIT). GL_DYNAMIC_STORAGE_BIT тут не нужен, ты не используешь функции GL для загрузки.
3) Ты не синхронизируешь, когда возвращаешься в начало буфера.
Примерная логика:
Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
GLsizeiptr off = 0;
GLsizeiptr stride = frameDataSize;
GLsizeiptr size = n * stride;
glNamedBufferStorage(pbo, size, nullptr, GL_MAP_WRITE_BIT);
 
.......................................
 
// загрузка очередного кадра:
void ptr = nullptr;
if (off == 0)
    ptr = glMapNamedBufferRange(pbo, off, stride,
                    GL_MAP_WRITE_BIT |
                    GL_MAP_INVALIDATE_BUFFER_BIT);
else
    ptr = glMapNamedBufferRange(pbo, off, stride,
                    GL_MAP_WRITE_BIT |
                    GL_MAP_INVALIDATE_RANGE_BIT |
                    GL_MAP_UNSYNCHRONIZED_BIT);
if (ptr)
{
    writeData(ptr, ptr + stride);
    glUnmapNamedBuffer(pbo);
    glTextureSubImage2D(...);
}
off = (off + stride) % size;
Сначала рендер(первый кадр с пустой текстурой), потом загрузка(для следующего кадра).
0
Модератор
Эксперт Java
 Аватар для alecss131
2835 / 1344 / 403
Регистрация: 11.08.2017
Сообщений: 4,297
Записей в блоге: 2
16.02.2021, 23:06  [ТС]
Цитата Сообщение от zayats80888 Посмотреть сообщение
1) У тебя тут off равен нулю.
конец функции init
C++
95
s = sz * h;
функция вызывается до выделения буфера
0
16.02.2021, 23:24

Не по теме:

Цитата Сообщение от alecss131 Посмотреть сообщение
конец функции init
ок, проглядел

0
Модератор
Эксперт Java
 Аватар для alecss131
2835 / 1344 / 403
Регистрация: 11.08.2017
Сообщений: 4,297
Записей в блоге: 2
16.02.2021, 23:38  [ТС]
Меня примерный код больше запутал, мне нужно в dframe.data[0] положить указатель куда будут писаться данные
Code
1
2
3
writeData(ptr, ptr + stride);
glUnmapNamedBuffer(pbo);
glTextureSubImage2D(...);
вот тут самое непонятное, по идее в glTextureSubImage2D должен быть другой оффсет, разве нет? хотя там ... ))
И если сделать GL_MAP_INVALIDATE_BUFFER_BIT то последующий glTextureSubImage2D сработает? ведь запрос будет отправлен после, а не до
0
 Аватар для zayats80888
6352 / 3523 / 1428
Регистрация: 07.02.2019
Сообщений: 8,995
17.02.2021, 00:16
Цитата Сообщение от alecss131 Посмотреть сообщение
вот тут самое непонятное, по идее в glTextureSubImage2D должен быть другой оффсет, разве нет?
Нет, тот что только что записали. glTextureSubImage2D будет асинхронно загружать в текстуру.
Цитата Сообщение от alecss131 Посмотреть сообщение
И если сделать GL_MAP_INVALIDATE_BUFFER_BIT то последующий glTextureSubImage2D сработает? ведь запрос будет отправлен после, а не до
GL_MAP_INVALIDATE_BUFFER_BIT используется только для реаллокации хранилища(когда "курсор" в начало попал). Оно целиком не валидное, но мы запишем в первый участок новые данные, unmap, и glTextureSubImage2D будет читать с этого участка. Предыдущие glTextureSubImage2D, если они не завершились, будут читать с предыдущего хранилища. Я же объяснял в посте №8.

---

И ещё, запроси glGetIntegerv(GL_MIN_MAP_BUFFER_ALIGNMEN T, ...), что бы подогнать stride.
0
Модератор
Эксперт Java
 Аватар для alecss131
2835 / 1344 / 403
Регистрация: 11.08.2017
Сообщений: 4,297
Записей в блоге: 2
17.02.2021, 12:38  [ТС]
Переписал код вот так
заголовок
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#ifndef _VIDEO_DECODER_HEADER_
#define _VIDEO_DECODER_HEADER_
#include <gl46.h>
#include <string>
extern "C" {
    #include <libavcodec/avcodec.h>
    #include <libavformat/avformat.h>
    #include <libswscale/swscale.h>
}
#ifndef NDEBUG
#include <iostream>
#endif
 
class VideoDecoder {
public :
    VideoDecoder(std::string fname, int n = 4);
    ~VideoDecoder();
    void process(GLuint tex);
private :
    void fillBuffer();
    void init(std::string fname);
    AVFormatContext* formatContext;
    int err;
    int videoStream;
    AVCodecContext* codecContext;
    AVCodec* codec;
    SwsContext* imgConvertContext;
    AVFrame* frame;
    AVPacket packet;
    AVFrame dframe;
    int w;
    int h;
    int s;
    GLuint pbo;
    int off;
    int all;
};
#endif
реализация
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
#include "VideoDecoder.h"
 
VideoDecoder::VideoDecoder(std::string fname, int n)
    : formatContext{}, err{}, videoStream{}, codecContext{}, codec{},
    imgConvertContext{}, frame{}, packet{}, dframe{}, w{}, h{},
    s{}, pbo{}, off{}, all{} {
    init(fname);
    glCreateBuffers(1, &pbo);
    all = s * n ;
    glNamedBufferStorage(pbo, all, nullptr, GL_MAP_WRITE_BIT);
#ifndef NDEBUG
    int alig;
    glGetIntegerv(GL_MIN_MAP_BUFFER_ALIGNMENT, &alig);
    if (all % alig != 0) {
        std::cerr << "Wrong alligment" << std::endl;
    }
#endif
}
 
VideoDecoder::~VideoDecoder() {
    glDeleteBuffers(1, &pbo);
    av_frame_unref(frame);
    av_frame_unref(&dframe);
    avformat_close_input(&formatContext);
    avcodec_free_context(&codecContext);
}
 
void VideoDecoder::process(GLuint tex) {
    fillBuffer();
    glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
    glTextureSubImage2D(tex, 0, 0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, (void *)off);
    glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
    off = (off + s) % all;
}
 
void VideoDecoder::fillBuffer() {
    GLubyte* ptr{};
    if (off == 0) {
        ptr = (GLubyte*)glMapNamedBufferRange(pbo, off, s, GL_MAP_WRITE_BIT
            | GL_MAP_INVALIDATE_BUFFER_BIT);
    }
    else {
        ptr = (GLubyte*)glMapNamedBufferRange(pbo, off, s, GL_MAP_WRITE_BIT
            | GL_MAP_INVALIDATE_RANGE_BIT | GL_MAP_UNSYNCHRONIZED_BIT);
    }
    while (av_read_frame(formatContext, &packet) >= 0) {
        if (packet.stream_index == videoStream) {
            err = avcodec_send_packet(codecContext, &packet);
            if (err == 0) {
                err = avcodec_receive_frame(codecContext, frame);
                if (err == 0) {
                    if (ptr) {
                        dframe.data[0] = ptr;
                        sws_scale(imgConvertContext, frame->data, frame->linesize, 0,
                            codecContext->height, dframe.data, dframe.linesize);
                        glUnmapNamedBuffer(pbo);
                    }
                }
                //else if (err == AVERROR(EAGAIN)) {
                //  continue;
                //}
#ifdef NDEBUG
            }
#else
                else {
                    std::cerr << "ffmpeg: Receive frame error: " << err << std::endl;
                }
            }
            else {
                std::cerr << "ffmpeg: Send packet error: " << err << std::endl;
            }
#endif
            break;
        }
        av_packet_unref(&packet);
    }
}
 
void VideoDecoder::init(std::string fname) {
    err = avformat_open_input(&formatContext, fname.c_str(), nullptr, nullptr);
#ifndef NDEBUG
    if (err < 0) {
        std::cerr << "ffmpeg: Unable to open input file" << std::endl;
        exit(-1);
    }
#endif
    err = avformat_find_stream_info(formatContext, nullptr);
#ifndef NDEBUG
    if (err < 0) {
        std::cerr << "ffmpeg: Unable to find stream info" << std::endl;;
        exit(-1);
    }
#endif
    for (unsigned int i = 0; i < formatContext->nb_streams; i++) {
        if (formatContext->streams[videoStream]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
            videoStream = i;
            break;
        }
    }
#ifndef NDEBUG
    if (videoStream == formatContext->nb_streams) {
        std::cerr << "ffmpeg: Unable to find video stream" << std::endl;
        exit(-1);
    }
#endif
    codec = avcodec_find_decoder(formatContext->streams[videoStream]->codecpar->codec_id);
    codecContext = avcodec_alloc_context3(codec);
    avcodec_parameters_to_context(codecContext, formatContext->streams[videoStream]->codecpar);
    err = avcodec_open2(codecContext, codec, nullptr);
#ifndef NDEBUG
    if (err < 0) {
        std::cerr << "ffmpeg: Unable to open codec" << std::endl;
        exit(-1);
    }
#endif
    w = codecContext->width;
    h = codecContext->height;
    imgConvertContext = sws_getCachedContext(nullptr, w, h, codecContext->pix_fmt,
        w, h, AV_PIX_FMT_RGBA, SWS_BICUBIC, nullptr, nullptr, nullptr);
#ifndef NDEBUG
    if (imgConvertContext == nullptr) {
        std::cerr << "Cannot initialize the conversion context" << std::endl;
        exit(-1);
    }
#endif
    frame = av_frame_alloc();
    int sz = w * 4;
    s = sz * h;
    dframe.linesize[0] = sz;
    dframe.channels = 4;
    dframe.width = w;
    dframe.height = h;
}
Спам нотификациями остался каждый кадр, но зато теперь не все видео открываются (пишет огибки ffmpeg: Receive frame error: -11), заодно убрал масштабирование видео, оставил только смену формата
Странно... мой первый вариант открывал все видео
Цитата Сообщение от zayats80888 Посмотреть сообщение
glGetIntegerv
сделал так и пока ни разу не получил вывода
C++
1
2
3
4
5
int alig;
glGetIntegerv(GL_MIN_MAP_BUFFER_ALIGNMENT, &alig);
if (all % alig != 0) {
    std::cerr << "Wrong alligment" << std::endl;
}
0
 Аватар для zayats80888
6352 / 3523 / 1428
Регистрация: 07.02.2019
Сообщений: 8,995
17.02.2021, 13:56
Цитата Сообщение от alecss131 Посмотреть сообщение
пишет огибки ffmpeg: Receive frame error: -11
OpenGL тут не причем, это где-то с ffmpeg косяк у тебя. Ну и в целом у тебя кривовато, например, в случае, если не записал данные(по какой либо причине), ты всё равно отправляешь их в текстуру и инкрементируешь off, это нужно делать если всё успешно, сразу после unmap.

Ну и узкое место ты нашел? У тебя же студия, покажи профилировку.
0
Модератор
Эксперт Java
 Аватар для alecss131
2835 / 1344 / 403
Регистрация: 11.08.2017
Сообщений: 4,297
Записей в блоге: 2
17.02.2021, 16:47  [ТС]
zayats80888, переписал вот так, видео стало воспроизводиться
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
void VideoDecoder::process(GLuint tex) {
    GLubyte* ptr{};
    if (off == 0) {
        ptr = (GLubyte*)glMapNamedBufferRange(pbo, off, s, GL_MAP_WRITE_BIT
            | GL_MAP_INVALIDATE_BUFFER_BIT);
    }
    else {
        ptr = (GLubyte*)glMapNamedBufferRange(pbo, off, s, GL_MAP_WRITE_BIT
            | GL_MAP_INVALIDATE_RANGE_BIT | GL_MAP_UNSYNCHRONIZED_BIT);
    }
    if (ptr) {
        dframe.data[0] = ptr;
        while (av_read_frame(formatContext, &packet) >= 0) {
            if (packet.stream_index == videoStream) {
                err = avcodec_send_packet(codecContext, &packet);
                if (err == 0) {
                    err = avcodec_receive_frame(codecContext, frame);
                    if (err == 0) {
                        sws_scale(imgConvertContext, frame->data, frame->linesize, 0,
                            codecContext->height, dframe.data, dframe.linesize);
                    }
                    else if (err == AVERROR(EAGAIN)) {
                        continue;
                    }
#ifdef NDEBUG
                }
#else
                    else {
                        std::cerr << "ffmpeg: Receive frame error: " << err << std::endl;
                    }
                }
                else {
                    std::cerr << "ffmpeg: Send packet error: " << err << std::endl;
                }
#endif
                break;
            }
            av_packet_unref(&packet);
        }
        glUnmapNamedBuffer(pbo);
        glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
        glTextureSubImage2D(tex, 0, 0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, (void*)off);
        glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
        off = (off + s) % all;
    }
}
Даже фпс на 4к стал 37-55, а не 22-25 как было раньше)) Если ставлю 10 кадров, то фпс ближе к 30, 2-4 то ближе к 50...

Цитата Сообщение от zayats80888 Посмотреть сообщение
это где-то с ffmpeg косяк
я в курсе, гугл выдал что эту ошибку стоит игнорить, просто пропускать кадр и обрабатывать следующий

Цитата Сообщение от zayats80888 Посмотреть сообщение
У тебя же студия, покажи профилировку.
Без понятия что это

Добавлено через 1 час 0 минут
Хм, странно, отключил вертикалку теперь 92 фпс, а если вывести фпс из ffmpeg то показывает в том же видео 25, в fullhd файлах фпс так вообще 200+
Надо будет на момент видео отключать вертикалку и ограничивать таймером
0
 Аватар для zayats80888
6352 / 3523 / 1428
Регистрация: 07.02.2019
Сообщений: 8,995
17.02.2021, 17:12
Цитата Сообщение от alecss131 Посмотреть сообщение
Без понятия что это
https://docs.microsoft.com/ru-... ew=vs-2019
Цитата Сообщение от alecss131 Посмотреть сообщение
переписал вот так
Ну ёмаё , опять, если кадр не запишется в mapped буфер, то ты впустую отправишь невалидные данные в текстуру. Мэпить нужно только когда кадр готов.
0
Модератор
Эксперт Java
 Аватар для alecss131
2835 / 1344 / 403
Регистрация: 11.08.2017
Сообщений: 4,297
Записей в блоге: 2
17.02.2021, 22:42  [ТС]
Цитата Сообщение от zayats80888 Посмотреть сообщение
Мэпить нужно только когда кадр готов.
Надеюсь так правильно
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
void VideoDecoder::process(GLuint tex) {
    while (av_read_frame(formatContext, &packet) >= 0) {
        if (packet.stream_index == videoStream) {
            err = avcodec_send_packet(codecContext, &packet);
            if (err == 0) {
                err = avcodec_receive_frame(codecContext, frame);
                if (err == 0) {
                    GLubyte* ptr{};
                    if (off == 0) {
                        ptr = (GLubyte*)glMapNamedBufferRange(pbo, off, s, GL_MAP_WRITE_BIT
                            | GL_MAP_INVALIDATE_BUFFER_BIT);
                    }
                    else {
                        ptr = (GLubyte*)glMapNamedBufferRange(pbo, off, s, GL_MAP_WRITE_BIT
                            | GL_MAP_INVALIDATE_RANGE_BIT | GL_MAP_UNSYNCHRONIZED_BIT);
                    }
                    if (ptr) {
                        dframe.data[0] = ptr;
                        sws_scale(imgConvertContext, frame->data, frame->linesize, 0,
                            codecContext->height, dframe.data, dframe.linesize);
                        glUnmapNamedBuffer(pbo);
                        glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
                        glTextureSubImage2D(tex, 0, 0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, (void*)off);
                        glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
                        off = (off + s) % all;
                    }
                }
                else if (err == AVERROR(EAGAIN)) {
                    continue;
                }
#ifdef NDEBUG
            }
#else
                else {
                    std::cerr << "ffmpeg: Receive frame error: " << err << std::endl;
                }
            }
            else {
                std::cerr << "ffmpeg: Send packet error: " << err << std::endl;
            }
#endif
            break;
        }
        av_packet_unref(&packet);
    }
}
Решил еще добавить зависимость фпс от фпс видео, отключил вертикалку, набросал класс, но теперь вечно 22фпс, не зависимо от видео, что 25 что 30 дают 22 (мониторю с помощью оверлея nvidia). Вот код
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#ifndef _TIMER_HEADER_
#define _TIMER_HEADER_
#include <chrono>
#include <thread>
#include <iostream>
 
class Timer {
public : 
    Timer(int fps);
    ~Timer() {};
    void start();
    void end();
private :
    std::chrono::duration<double> frameTimeMin;
    std::chrono::high_resolution_clock::time_point clock;
    std::chrono::high_resolution_clock::time_point now;
    std::chrono::duration<double> frameTime;
};
#endif
//реализация
#include "Timer.h"
 
Timer::Timer(int fps) : frameTimeMin{ 1.0f / fps }, clock{}, now{}, frameTime{} {}
 
void Timer::start() {
    clock = std::chrono::high_resolution_clock::now();
}
 
void Timer::end() {
    now = std::chrono::high_resolution_clock::now();
    frameTime = now - clock;
    std::this_thread::sleep_for(frameTimeMin - frameTime);
}
Использую так, в начале цикла вызываю start(), в самом конце end(). Похоже сон дольше чем нужно.
0
 Аватар для zayats80888
6352 / 3523 / 1428
Регистрация: 07.02.2019
Сообщений: 8,995
17.02.2021, 23:45
alecss131, читай доки. Я с ffmpeg не работал, но вкратце глянул, там каждый кадр сопровождается метками времени.Судя по тому, что фпс проседает при синхронизации - основной тормоз, это декодирование видео(хотя профилировку ты так и не показал).
Я бы сделал так:
В главном потоке создал бы экземпляр класса, поставляющего кадр(текстуру) для рендера, с примерно таким функционалом(!псевдокод!):
Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
// внутри класса примерно такая начинка
struct FrameGL
{
    GLuint texture; // текстура с кадром
    GLuint pbo; // буфер для трансфера(для одного кадра)
    Time timePoint; // временная метка кадра
};
 
FrameGL current, next; // текущий и следующий кадры(если есть возможность, то можно загружать в видюху и побольше, чем 2)
ThreadSafeQueue<AVFrame*> frameDataQueue; // очередь подготовленных кадров(потокобезопасная)
Timer timer; // таймер, для смены кадров
 
//...
 
// метод возвращет текстуру с кадром
// будет возвращать одну и туже текстуру, если время ещё не пришло
// а тем временем следующий кадр будет асинхронно загружаться в текстуру
GLuint getFrame()
{
    // если пришло время кадра
    if (timer.now() >= nextFrame.timePoint)
    {
        swap(current, next); // свапаем(следующий становится текущим)
        // если очередь пустая, ждём следующий кадр и вынимаем его
        AVframe * frame = frameDataQueue.waitAndPop();
        // загружаем следующий кадр в текстуру (map, sws_scale, unmap, SubImage2D) и ставим метку времени
        // это относительно дешёвая операция, в сравнении с декодингом, я думаю
        uploadFrame(next, frame);
        // освобождаем ресурсы или отдаём обратно потоку декодирования
        releaseResources(frame);
    }
    return current.texture; // возвращаем текущий кадр
}
 
// сам декодировщик работает асинхронно в другом потоке, подготавливая кадры
// ограничение на количество кадров, которые могут находится в очереди выбираешь по памяти(например на 10 кадров).
// Т.е. поток декодера спит, если очередь полная, и просыпается когда освобождается место.
void decodeThread()
{
    for(; !finished;)
    {
        
        AVFrame * frame = decodeNextFrame();
        // ждём свободное место в очереди, если полная
        // и отправляем основному потоку на загрузку
        frameDataQueue.waitAndPush(frame);
    }
}
 
// Главный поток
for(;;)
{
    //...
    renderFrame(decoder.getFrame());
    pollEvents();
    swapBuffers();
}
Ну и разумеется всё это нужно писать с грамотной обработкой ошибок и исключительных ситуаций,
правильным управлением ресурсами и разделением обязанностей.
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
BasicMan
Эксперт
29316 / 5623 / 2384
Регистрация: 17.02.2009
Сообщений: 30,364
Блог
17.02.2021, 23:45
Помогаю со студенческими работами здесь

Наложить видео с альфа каналом поверх картинки с камеры, чтобы видео не перекрывало картинку
всем привет! стоит такая задача: есть видео в контейнере .mov, которое содержит альфаканал нужно вывести картинку с камеры, а поверх...

Воспроизведение видео
Имеется форма на которой расположены TPanel и TMediaPlayer. Видео воспроизводится, всё работает, но одно НО. После закрытия формы, видео...

воспроизведение видео на пк
как вылечить данную болезнь на компьютере? картинка 1 видео с пк а картинка 2 с интернета... как будто цветов не хватает на пк

Воспроизведение видео
Я извеняюсь зато что может быть не в тему, но меня интересует как можно воспроизвести видео по такой ссылке: udp://@225.225.225.4:1234 ...

Воспроизведение видео
Подскажите что не так, ошибка когда нажимаю на PictureBox. using System; using System.Collections.Generic; using...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
20
Ответ Создать тему
Новые блоги и статьи
Новый ноутбук
volvo 07.12.2025
Всем привет. По скидке в "черную пятницу" взял себе новый ноутбук Lenovo ThinkBook 16 G7 на Амазоне: Ryzen 5 7533HS 64 Gb DDR5 1Tb NVMe 16" Full HD Display Win11 Pro
Музыка, написанная Искусственным Интеллектом
volvo 04.12.2025
Всем привет. Некоторое время назад меня заинтересовало, что уже умеет ИИ в плане написания музыки для песен, и, собственно, исполнения этих самых песен. Стихов у нас много, уже вышли 4 книги, еще 3. . .
От async/await к виртуальным потокам в Python
IndentationError 23.11.2025
Армин Ронахер поставил под сомнение async/ await. Создатель Flask заявляет: цветные функции - провал, виртуальные потоки - решение. Не threading-динозавры, а новое поколение лёгких потоков. Откат?. . .
Поиск "дружественных имён" СОМ портов
Argus19 22.11.2025
Поиск "дружественных имён" СОМ портов На странице: https:/ / norseev. ru/ 2018/ 01/ 04/ comportlist_windows/ нашёл схожую тему. Там приведён код на С++, который показывает только имена СОМ портов, типа,. . .
Сколько Государство потратило денег на меня, обеспечивая инсулином.
Programma_Boinc 20.11.2025
Сколько Государство потратило денег на меня, обеспечивая инсулином. Вот решила сделать интересный приблизительный подсчет, сколько государство потратило на меня денег на покупку инсулинов. . . .
Ломающие изменения в C#.NStar Alpha
Etyuhibosecyu 20.11.2025
Уже можно не только тестировать, но и пользоваться C#. NStar - писать оконные приложения, содержащие надписи, кнопки, текстовые поля и даже изображения, например, моя игра "Три в ряд" написана на этом. . .
Мысли в слух
kumehtar 18.11.2025
Кстати, совсем недавно имел разговор на тему медитаций с людьми. И обнаружил, что они вообще не понимают что такое медитация и зачем она нужна. Самые базовые вещи. Для них это - когда просто люди. . .
Создание Single Page Application на фреймах
krapotkin 16.11.2025
Статья исключительно для начинающих. Подходы оригинальностью не блещут. В век Веб все очень привыкли к дизайну Single-Page-Application . Быстренько разберем подход "на фреймах". Мы делаем одну. . .
Фото: Daniel Greenwood
kumehtar 13.11.2025
Расскажи мне о Мире, бродяга
kumehtar 12.11.2025
— Расскажи мне о Мире, бродяга, Ты же видел моря и метели. Как сменялись короны и стяги, Как эпохи стрелою летели. - Этот мир — это крылья и горы, Снег и пламя, любовь и тревоги, И бескрайние. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2025, CyberForum.ru