Push-стриминг на основе интерфейса Gstreamer и DJI OSDK4.0 video h264

Предпосылки
Чтобы реализовать потоковую передачу в реальном времени и обработку изображений видео с БПЛА, сначала должны быть завершены кодирование и декодирование видео. Видеоинтерфейс DJI действительно слишком изрезан! Я ознакомился со многими статьями великих богов, большинство из которых расшифровывают локальные файлы или напрямую извлекают потоки с сервера, что не может обеспечить требуемого мне динамического декодирования потоков в реальном времени. Через полмесяца я наконец могу декодировать в реальном времени. Надеюсь, результаты моих исследований могут помочь больше. Несколько человек.

  1. Основным форматом сжатия видео является h264 (кодирование IDR), есть много связанных руководств, и почти нет контента, связанного с кодированием GDR, который не может быть декодирован путем извлечения nalu из потока байтов;
  2. Непосредственно сэмплируйте видео с самолета и сохраните его как локальный файл, и его нельзя воспроизводить напрямую.После преобразования в формат mp4 при воспроизведении будет размытый экран;
  3. Слишком высокая нагрузка на процессор интерфейса программного обеспечения DJI.

Формат кодирования видео основной камеры H20 DJI M300 - h264 (GDR), а OSDK-4.0 предоставляет три образца видеоинтерфейса камеры самолета.

  1. OSDK вызывает ffmpeg для программного декодирования необработанного потока h264, отправляет массив Mat, доступный для чтения OpenCV, и напрямую отображает кадр изображения;
  2. На основе первого интерфейса добавляются цикл и спящий режим, и аналоговый видеопоток кадра изображения запускается непрерывно;
  3. Прямое предоставление функции простого потока h264 (GDR), исходящий параметр - это адрес пакета данных и длина пакета данных.

проблема

Возникла проблема. После инициализации параметров, связанных с камерой, интерфейс, предоставляемый DJI, будет циклически вызывать функцию liveView, передаваемую пакетом данных. Подключаемый модуль GStreamer appsrc, который импортирует внешние данные, также должен циклически вызывать функцию need-data. Две циклические функции обратного вызова выполняются одновременно. Чтобы синхронизировать взаимодействие с данными, я разработал буфер кольцевой очереди для хранения общедоступных данных и использовал блокировку мьютекса с условными переменными, чтобы гарантировать синхронизацию двух потоков.

достичь

Конец отправки:

/***************************************
@Copyright(C) All rights reserved.
@Author DENG Ganglin
@Date   2020.08
***************************************/

#include "dji_vehicle.hpp"
#include "dji_linux_helpers.hpp"

#include <gst/gst.h>
#include <gst/gstminiobject.h>
#include <glib.h>

#include <stdlib.h>
#include <pthread.h>
#include <iostream>
#include <sys/stat.h>
#include <sys/types.h>
#include <queue>
#include <pthread.h>
#include <unistd.h>

#include "loopqueue.hpp"

using namespace DJI::OSDK;
using namespace std;

// 创建插件
GstElement *pipeline, *appsrc, *parse, *decoder, *scale, *filter1, *conv, *encoder, *filter2, *rtppay, *udpsink;
GstCaps *scale_caps;
GstCaps *omx_caps;
static GMainLoop *loop;
GstBuffer *buf;

// 定义结构体,保存生产者的数据
typedef struct Msg
{
    
    
	int len;
	uint8_t* data;
}msg;

// 创建循环队列
LoopQueue<msg*> loopqueue(256);

pthread_mutex_t myMutex = PTHREAD_MUTEX_INITIALIZER;  // 初始化互斥锁
pthread_cond_t  cond = PTHREAD_COND_INITIALIZER;  // 初始化条件变量

int count_t = 0; // 打印检查数据流动是否正常

static void cb_need_data(GstElement *appsrc, guint size, gpointer user_data)
{
    
    
	static GstClockTime timestamp = 0;
	GstFlowReturn ret;
	GstMapInfo map;

	count_t++;

	// 给互斥量加锁
	pthread_mutex_lock(&myMutex);

	// 判断条件变量是否满足 访问公共区条件
	while (0 == loopqueue.getSize())
	{
    
    
		pthread_cond_wait(&cond, &myMutex);
	}
	// 访问公共区, 取数据 
	// 往appsrc输入数据
	buf = gst_buffer_new_allocate(NULL, loopqueue.top()->len, NULL);
	gst_buffer_map(buf, &map, GST_MAP_WRITE);
	memcpy((guchar *)map.data, (guchar *)(loopqueue.top()->data), loopqueue.top()->len);

	printf("count_t = %d, bufLen = %d\n", count_t, loopqueue.top()->len);
	// 给互斥量解锁
	pthread_mutex_unlock(&myMutex);

	GST_BUFFER_PTS(buf) = timestamp;
	GST_BUFFER_DURATION(buf) = gst_util_uint64_scale_int(1, GST_SECOND, 1);
	timestamp += GST_BUFFER_DURATION(buf);
	g_signal_emit_by_name(appsrc, "push-buffer", buf, &ret);

	gst_buffer_unref(buf);
	if (ret != 0)
	{
    
    
		g_main_loop_quit(loop);
	}

	// 释放摘下的结点
	delete loopqueue.top()->data;
	delete loopqueue.top();
	loopqueue.pop();
}

void liveViewSampleCb(uint8_t* buf264, int bufLen, void* userData)
{
    
    
	// 生产数据
	msg *mp = new msg;
	mp->len = bufLen;
	mp->data = new uint8_t[bufLen];
	memcpy(mp->data, buf264, bufLen);
	// 加锁
	pthread_mutex_lock(&myMutex);
	// 访问公共区
	loopqueue.push(mp);
	// 解锁
	pthread_mutex_unlock(&myMutex);
	// 唤醒阻塞在条件变量上的消费者
	pthread_cond_signal(&cond);
	return;
}

// 创建管道,触发need-data信号
void *consumer(void *arg)
{
    
    
	// init GStreamer
	gst_init(NULL, NULL);
	loop = g_main_loop_new(NULL, FALSE);

	// setup pipeline
	pipeline = gst_pipeline_new("pipeline");
	appsrc = gst_element_factory_make("appsrc", "source");
	parse = gst_element_factory_make("h264parse", "parse");
	decoder = gst_element_factory_make("avdec_h264", "decoder");
	scale = gst_element_factory_make("videoscale", "scale");
	filter1 = gst_element_factory_make("capsfilter", "filter1");
	conv = gst_element_factory_make("videoconvert", "conv");
	encoder = gst_element_factory_make("omxh264enc", "encoder");
	filter2 = gst_element_factory_make("capsfilter", "filter2");
	rtppay = gst_element_factory_make("rtph264pay", "rtppay");
	udpsink = gst_element_factory_make("udpsink", "sink");

	scale_caps = gst_caps_new_simple("video/x-raw",
		"width", G_TYPE_INT, 1280,
		"height", G_TYPE_INT, 720,
		NULL);

	omx_caps = gst_caps_new_simple("video/x-h264",
		"stream-format", G_TYPE_STRING, "byte-stream",
		NULL);

	g_object_set(G_OBJECT(filter1), "caps", scale_caps, NULL);
	g_object_set(G_OBJECT(filter2), "caps", omx_caps, NULL);
	gst_caps_unref(scale_caps);
	gst_caps_unref(omx_caps);

	g_object_set(udpsink, "host", "127.0.0.1", NULL);
	g_object_set(udpsink, "port", 5555, NULL);
	g_object_set(udpsink, "sync", false, NULL);
	g_object_set(udpsink, "async", false, NULL);

	// setup	
	//g_object_set(G_OBJECT(appsrc), "caps",
	//	gst_caps_new_simple("video/x-raw",
	//		"stream-format", G_TYPE_STRING, "byte-stream",
	//		NULL), NULL);

	gst_bin_add_many(GST_BIN(pipeline), appsrc, parse, decoder, scale, filter1, conv, encoder, filter2, rtppay, udpsink, NULL);
	gst_element_link_many(appsrc, parse, decoder, scale, filter1, conv, encoder, filter2, rtppay, udpsink, NULL);

	// setup appsrc
	//g_object_set(G_OBJECT(appsrc),
	//	"stream-type", 0,
	//	"format", GST_FORMAT_TIME, NULL);

	g_signal_connect(appsrc, "need-data", G_CALLBACK(cb_need_data), NULL);

	/* play */
	std::cout << "start sender pipeline" << std::endl;
	gst_element_set_state(pipeline, GST_STATE_PLAYING);
	g_main_loop_run(loop);
	/* clean up */
	gst_element_set_state(pipeline, GST_STATE_NULL);
	gst_object_unref(GST_OBJECT(pipeline));
	g_main_loop_unref(loop);
	gst_buffer_unref(buf);

}

// 初始化飞机开始推送264码流数据包
void *producer(void *arg)
{
    
    
	while (true)
	{
    
    
		// 启动 OSDK.
		bool enableAdvancedSensing = true;
		LinuxSetup linuxEnvironment(1, NULL, enableAdvancedSensing);
		Vehicle *vehicle = linuxEnvironment.getVehicle();
		if (vehicle == nullptr)
		{
    
    
			std::cout << "Vehicle not initialized, exiting.\n";
			return NULL;
		}
		vehicle->advancedSensing->startH264Stream(LiveView::OSDK_CAMERA_POSITION_NO_1,
			liveViewSampleCb,
			NULL);
		sleep(100); //可根据需求定义回调函数一次调用的时间
	}
}

int main(int argc, char** argv)
{
    
    
	// 创建一个生产者线程和一个消费者线程
	int rc1, rc2;
	pthread_t pid, cid;

	if (rc1 = pthread_create(&pid, NULL, producer, NULL))
		cout << "producer thread creation failed: " << rc1 << endl;
	if (rc2 = pthread_create(&cid, NULL, consumer, NULL))
		cout << "consumer thread creation failed: " << rc2 << endl;

	pthread_join(pid, NULL);
	pthread_join(cid, NULL);

	pthread_mutex_destroy(&myMutex);
	pthread_cond_destroy(&cond);
	return 0;
}

Конец приема:

/***************************************
@Copyright(C) All rights reserved.
@Author DENG Ganglin
@Date   2020.08
***************************************/

#include "opencv2/opencv.hpp"
#include <cstdio>

using namespace cv;
using namespace std;

int main(int argc, char** argv)
{
    
    
	VideoCapture cap("udpsrc port=5555 ! application/x-rtp,encoding-name=H264 ! rtpjitterbuffer latency=0 ! rtph264depay ! avdec_h264 ! videoconvert ! appsink");

	if( !cap.isOpened())
	{
    
    
		cout<<"Fail"<<endl;
		return -1;
	}

	namedWindow("frame",1);
	for(;;)
	{
    
    
		Mat frame(1280,720, CV_8UC3, Scalar(0,0,255));
		cap >> frame; // get a new frame from camera

		imshow("frame", frame);

		if(waitKey(30) >= 0) break;
	}
	return 0;
}

подводить итоги

  1. Код hpp для кольцевой очереди отсутствует, он не оригинальный, в интернете много;
  2. Маленькая глупая ошибка, заявка на память new uint8_t [bufLen] записывается как new uint8_t (bufLen), что эквивалентно заявке только на память uint8_t, и программа продолжает сообщать о недопустимой памяти при запуске. Этого не должно быть;
  3. Измеренный отправитель может использовать мягкое кодирование только в первый раз после синтаксического анализа чистого потока, а для последующего декодирования можно использовать жесткое декодирование.Загрузка ЦП составляет около 180%, что немного ниже 250% для полного мягкого декодирования;
  4. Фактический эффект неплох, задержка около 800 мс, позже ее можно оптимизировать.

рекомендация

отblog.csdn.net/Collin_D/article/details/108177398