Задача заключалась в следующем:
1. Написать программу сервер, которая загружает видео из файла, сжимает в JPEG и передает по протоколу TCP программе клиенту.
2. Написать программу клиент, которая принимает видео по TCP, декодирует и отображает.
Описанные выше задачи являются элементарными и служат для того, чтобы «отработать» технологию. Кажется, что эта тема уже описана давно, но после некоторого времени в поиске готового ответа (куска кода) я понял, что не все очевидно. Поэтому здесь я привожу свой опыт в этом вопросе. Возможно кому-то мой опыт окажется полезным.
Программу разрабатывал в среде Visual Studio 2015 с использование библиотеки OpenCV версии 3.1. Опущу этапы создания проекта, подключения библиотек и написание кода, отвечающего за сетевое взаимодействие. В конце статьи дам ссылку на исходные коды проекта с полными коментариями в коде для быстрого понимания. Сосредоточимся на главной проблеме: как получить видео из файла, сжать его в JPEG с нужной степенью компрессии и передать по сети с дальнейшим декодированием и отображением на приемной стороне. Ниже небольшой кусок кода, показывающий как сжать кадр видео и передать его по сети (с учетом того, что соединение с приемной стороной установлено).
// Объявляем переменные
Mat srcMat; // Данные исходного изображения
vector<uchar> imgBuf; // Буфер для сжатого изображения
vector<int> quality_params = vector<int>(2); // Вектор параметров качества сжатия
quality_params[0] = CV_IMWRITE_JPEG_QUALITY; // Кодек JPEG
quality_params[1] = 20; // По умолчанию качество сжатия (95) 0-100
// Захватываем кадр видео
cd.frame = cvQueryFrame(cd.videocap);
// Получаем изображение в вектор
srcMat = cv::cvarrToMat(cd.frame);
// Кодируем изображение кодеком JPEG
imencode(".jpg", srcMat, imgBuf, quality_params);
// Отправляем данные
send(clientSocket, (const char*)&imgBuf[0], static_cast<int>(imgBuf.size()), 0);
Теперь опишем как распаковать изображение на приемной стороне. Ниже фрагмент кода программы клиента.
// Объявляем переменные
int iResult; // Переменная на результат операций
const int MAX_BUF_SIZE = 2073600; // Произвольно максимальный размер приемного буфера
unsigned char *buf = new unsigned char[MAX_BUF_SIZE]; // Буфер для прима сообщений
vector<uchar> videoBuffer; // Буфер данных изображения
Mat jpegimage; // Вектор данных изображения
IplImage img; // Изображение для вывода
// Ожидаем прихода данных
iResult = recv(connectSocket, (char *)&buf[0], MAX_BUF_SIZE, 0);
if (iResult > 0) {
// Если пришли данные изображения, копируем их
videoBuffer.resize(iResult);
memcpy((char*)(&videoBuffer[0]), buf, iResult);
// Декодируем данные
jpegimage = imdecode(Mat(videoBuffer), CV_LOAD_IMAGE_COLOR);
img = jpegimage;
// Выводим изображение
cvShowImage("Recieved Video", &img);
// Ожидаем отклика управления (произвольно 5 ms)
cvWaitKey(5);
}//if (iResult > 0)...
Описанные операции производятся для каждого кадра видео. Надеюсь этот небольшой пример поможет решить возникающие трудности сжатия и передачи потокового видео через TCP используя библиотеку OpenCV. Ниже по ссылке скриншот работы клиента и сервера.
www.dropbox.com/s/ikisl8rjxxd5d0f/%D0%91%D0%B5%D0%B7%D1%8B%D0%BC%D1%8F%D0%BD%D0%BD%D1%8B%D0%B9.png
Ввиду того, что коэффициент сжатия стоит 20 (высокое сжатие) на приемной стороне можно заметить значительные искажения картинки.
Исходные коды программ сервера и клиента с подробными комментариями вы можете посмотреть по ссылкам:
www.dropbox.com/s/3ucjsdes7khcr24/Server.cpp?dl=0
www.dropbox.com/s/14mat8bhlonz392/Client.cpp?dl=0
Удачи во всех начинаниях!
Комментарии (12)
RPG18
17.05.2016 15:55Удивительно что не был сделан MJPEG over HTTP(Захват видео с сетевых камер, часть 1)
Sirius_Voodoo
17.05.2016 21:32-7Ого! Не был полдня и уже столько всего! Спасибо за Ваши комментарии. Учту в дальнейшем. Приятно, что такое обсуждение. Сразу видно, что РУССКИЙ форум — столько льется… )))
batChar
18.05.2016 10:18+5Забавно, никакой оскорбительной, нецензурной брани в вашу сторону сказано небыло. Конструктивные вопросы коментаторов вы проигнорировали. На что вы обиделись?
Как уже заметили, использовать «комбайн» OpenCV для кодирования и декодирования затея не очень разумная. Да и TCP для видеопотока. Ну да ладно, что нового несет в себе ваша сатья?
Ну и как вишенка сорцы в дропбоксе :)
amelmac
17.05.2016 21:37+1unsigned char *buf = new unsigned char[MAX_BUF_SIZE];
Raw pointer лучше не использовать для обладания памятью (на случай exception'a), лучше было бы так:
std::unique_ptr buf(new unsigned char[MAX_BUF_SIZE]);
Vik_007
17.05.2016 21:37Чего придрались, он же написал «отработать» технологию, нормально всё, кому надо тот допишет )
ksergey01
17.05.2016 21:37+4>> const int MAX_BUF_SIZE = 2073600;
Для такого принято использовать size_t
>>unsigned char *buf = new unsigned char[MAX_BUF_SIZE]; // Буфер для прима сообщений
>>vector videoBuffer;
Почему тогда для buf не использовать vector?
>> iResult = recv(connectSocket, (char *)&buf[0], MAX_BUF_SIZE, 0);
>> if (iResult > 0) {
откуда уверенность, что в буфере достаточно данных для декодирования?
>> // Если пришли данные изображения, копируем их
>> videoBuffer.resize(iResult);
>> memcpy((char*)(&videoBuffer[0]), buf, iResult);
>> // Декодируем данные
>> jpegimage = imdecode(Mat(videoBuffer), CV_LOAD_IMAGE_COLOR);
Зачем лишний раз копировать данные?
Печально видеть такое в хабе C++
a1ien_n3t
17.05.2016 23:30+1Что я толькочто прочел…
Зачем вам TCP при передачи «видео».
Почем нельзя было взять ffmpeg или gstreamer и написать в 5 строчек и сжимать вочто угодно.
Да и вобще можно без единой строки кода эту задачу решить. Темиже ffmpeg и gstremer вам в помошь.
Картинка с буханка хлеба и троллейбусSirius_Voodoo
18.05.2016 00:33+1Конечно можно все сделать проще. Это же не боевая программа. Это программа для того, чтобы попробовать как это работает и работает ли вообще (упаковка, пересылка и распаковка). В некоторых задачах может пригодиться кому-нибудь.
ice2heart
Трафика много будет при такой передаче, это как передавать каждый кадр ключевой. Не проще было бы использовать например fragmented mp4?
siemdi
Присоеденюсь, а почему ты выбрал именно JPEG? Явный костыль…