GstreamerおよびDJIOSDK4.0ビデオh264インターフェイスに基づくプッシュストリーミング

背景
UAVビデオのリアルタイムストリーミングと画像処理を実現するには、最初にビデオのエンコードとデコードを完了する必要があります。DJIのビデオインターフェイスは本当に多すぎます。私は多くの偉大な神の記事を調べましたが、そのほとんどはローカルファイルのデコードやサーバーからのストリームの直接プルであり、希望するリアルタイムの動的ストリームデコードを実現できません。半月後にようやくリアルタイムでデコードできるようになりました。私の研究結果がさらに役立つことを願っています。複数の人。

  1. 主流のビデオ圧縮形式はh264(IDRエンコーディング)であり、関連するチュートリアルが多数あり、バイトストリームからnaluを抽出してデコードできないGDRエンコーディングに関連するコンテンツはほとんどありません。
  2. 航空機のビデオを直接サンプリングしてローカルファイルとして保存し、直接再生することはできません。mp4形式に変換した後、再生時に画面がぼやけます。
  3. DJIのソフトソリューションインターフェイスのcpu負荷が高すぎます。

DJI M300のH20メインカメラビデオエンコーディングフォーマットはh264(GDR)であり、OSDK-4.0は3つの航空機カメラビデオインターフェイスサンプルを提供します。

  1. OSDKはffmpegを呼び出してh264rawストリームをソフトデコードし、OpenCVで読み取り可能なMat配列を送信し、画像のフレームを直接表示します。
  2. 最初のインターフェースに基づいて、ループとスリープが追加され、イメージフレームのアナログビデオストリームが継続的に起動されます。
  3. h264(GDR)ベアストリーム機能を直接提供します。発信パラメータはデータパケットアドレスとデータパケット長です。

問題

問題があります。カメラ関連のパラメータを初期化した後、DJIが提供するインターフェイスは、データパケットによってプッシュされたliveView関数を周期的にコールバックします。外部データをインポートするGStreamerプラグインappsrcも、need-data関数を周期的にコールバックする必要があります。2つの周期的なコールバック関数は同時に実行されます。データの相互作用を同期するために、パブリックデータを格納する循環キューバッファを設計し、条件変数を使用してミューテックスロックを使用して、2つのスレッドが同期されるようにしました。

成し遂げる

送信終了:

/***************************************
@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]は新しいuint8_t(bufLen)として書き込まれます。これは、uint8_tメモリの申請のみと同等であり、プログラムは実行時に不正なメモリを報告し続けます。
  3. 測定された送信者は、ベアストリームを解析した後の最初のエンコードにのみソフトコーディングを使用でき、ハードデコードは後続のデコードに使用できます。cpu負荷は約180%であり、完全なソフトソリューションの250%よりわずかに低くなっています。
  4. 実際の効果は悪くなく、遅延は約800msで、後で最適化できます。

おすすめ

転載: blog.csdn.net/Collin_D/article/details/108177398