gstreamer读取USB摄像头H264帧并用rtmp推流


由于要在嵌入式端使用rtmp推流,目前我知道的有三种办法,ffmpeg、gstreamer、librtmp,每一种都须要移植到嵌入式平台,仍是从我最熟悉的gstreamer开始验证吧。
如今个人嵌入式平台gstreamer库没有rtmp元件,所以只能先在Ubuntu16.04系统的PC上测试,而后再移植带有rtmp元件的gstreamer库。
Ubuntu16.04系统已经自带了gstreamer-1.0的库,而且已经包含rtmp元件,不用移植能够直接测试了。
注意:我使用的USB摄像头能够直接输出H264帧,所以不须要使用编码元件。

gstreamer命令行实现rtmp推流

首先用命令行工具测试:html

gst-launch-1.0 -v v4l2src device=/dev/video0 ! 'video/x-h264, width=1280, height=720, framerate=30/1' ! queue !  h264parse ! flvmux ! rtmpsink location='rtmp://192.168.1.102/live'

这个命令行执行后,就能够在192.168.1.102地址的PC上打开流媒体服务端观看。可使用nginx或者srs流媒体服务端,建立一个html文件打开网页观看。nginx

<h1>01</h1>
	<object width='640' height='377' id='SampleMediaPlayback' name='SampleMediaPlayback' type='application/x-shockwave-flash' classid='clsid:d27cdb6e-ae6d-11cf-96b8-444553540000' >
		<param name='movie' value='swfs/SampleMediaPlayback.swf' /> 
		<param name='quality' value='high' /> 
		<param name='bgcolor' value='#000000' /> 
		<param name='allowfullscreen' value='true' /> 
		<embed src='SampleMediaPlayback.swf' width='640' height='377' id='SampleMediaPlayback' quality='high' bgcolor='#000000' name='SampleMediaPlayback' allowfullscreen='true' pluginspage='http://www.adobe.com/go/getflashplayer' flashvars='&src=rtmp://192.168.1.102:1935/live&autoHideControlBar=true&streamType=live&autoPlay=true&verbose=true' type='application/x-shockwave-flash'> 
		</embed>
	</object>

gstreamer代码实现rtmp推流

#include <string.h>
#include <gst/gst.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

//gst-launch-1.0 -v v4l2src device=/dev/video0 ! 'video/x-h264, width=640, height=360, framerate=30/1' ! queue ! h264parse ! flvmux ! rtmpsink location='rtmp://192.168.1.102/live'

typedef struct _GstDataStruct
{
	GstElement *pipeline;
	GstElement *v4l2src;
	GstElement *queue;
	GstElement *h264parse;
	GstElement *flvmux;
	GstElement *rtmpsink;
	GstBus *bus;
	guint bus_watch_id;
	guint sourceid;        /* To control the GSource */
	GMainLoop *loop;  /* GLib's Main Loop */
} GstDataStruct;

static GstDataStruct GstData;
static unsigned int frame_width;
static unsigned int frame_height;
static unsigned int frame_rate;
static unsigned int frame_bps;
static char devname[32] = {0};

gboolean bus_msg_call(GstBus *bus, GstMessage *msg, GstDataStruct *pGstData)
{
	gchar *debug;
	GError *error;
	GMainLoop *loop = pGstData->loop;

	GST_DEBUG ("got message %s",gst_message_type_get_name (GST_MESSAGE_TYPE (msg)));
	switch (GST_MESSAGE_TYPE(msg))
	{
		case GST_MESSAGE_EOS:
			printf("End of stream\n");
			g_main_loop_quit(loop);
			break;
		case GST_MESSAGE_ERROR:
			gst_message_parse_error(msg, &error, &debug);
			g_free(debug);
			g_printerr("Error: %s\n", error->message);
			g_error_free(error);
			g_main_loop_quit(loop);
			break;
		default:
			break;
	}
	return TRUE;
}

int main(int argc, char *argv[])
{
	if(argc != 6)
	{
		frame_width = 1280;
		frame_height = 720;
		frame_rate = 30;
		frame_bps = 1500000;
		sprintf(devname, "%s", "/dev/video0");
	}
	else
	{
		frame_width = atoi(argv[2]);
		frame_height = atoi(argv[3]);
		frame_rate = atoi(argv[4]);
		frame_bps = atoi(argv[5]);
		sprintf(devname, "%s", argv[1]);
	}
	printf("width:%d, height:%d, rate:%d, bps:%d, dev:%s\n", frame_width, frame_height, frame_rate, frame_bps, devname);

	printf("============= v4l2 rtmp gst init start ============\n");
	gst_init (NULL, NULL);
	printf("=========== create v4l2 rtmp pipeline =============\n");
	GstData.pipeline           	= gst_pipeline_new ("v4l2_rtmp");
	GstData.pipeline           	= gst_pipeline_new ("v4l2_rtmp");
	GstData.v4l2src        	   	= gst_element_factory_make ("v4l2src",      "v4l2src");
	GstData.queue      		   	= gst_element_factory_make ("queue",  		"queue");
	GstData.h264parse      	   	= gst_element_factory_make ("h264parse",	"h264parse");
	GstData.flvmux           	= gst_element_factory_make ("flvmux",      	"flvmux");
	GstData.rtmpsink            = gst_element_factory_make ("rtmpsink",     "rtmpsink");

	if (!GstData.pipeline || !GstData.v4l2src || !GstData.queue ||
		!GstData.h264parse || !GstData.flvmux || !GstData.rtmpsink)
	{
		g_printerr ("One element could not be created... Exit\n");
		return -1;
	}

	printf("============ link v4l2 rtmp pipeline ==============\n");
	GstCaps *caps_v4l2src;
	caps_v4l2src = gst_caps_new_simple("video/x-h264", "stream-format", G_TYPE_STRING,"byte-stream",
									   "alignment", G_TYPE_STRING, "au",
									   "width", G_TYPE_INT, frame_width,
									   "height", G_TYPE_INT, frame_height,
									   "framerate",GST_TYPE_FRACTION, frame_rate, 1, NULL);
	GstCaps *caps_flv_sink;
	caps_flv_sink = gst_caps_new_simple("video/x-h264", "stream-format", G_TYPE_STRING,"avc",
									    "alignment", G_TYPE_STRING, "au",
									    "width", G_TYPE_INT, frame_width,
									    "height", G_TYPE_INT, frame_height,
									    "framerate",GST_TYPE_FRACTION, frame_rate, 1, NULL);

	g_object_set(G_OBJECT(GstData.v4l2src), "device", devname, NULL);
	g_object_set(G_OBJECT(GstData.rtmpsink), "location", "rtmp://192.168.1.102/live", NULL);
//注意:此处的location参数表明rtmp的url,其取值必须与html文件的rtmp的URL保持一致,才可观看视频。
	GstData.bus = gst_pipeline_get_bus(GST_PIPELINE(GstData.pipeline));
	GstData.bus_watch_id = gst_bus_add_watch(GstData.bus, (GstBusFunc)bus_msg_call, (gpointer)&GstData);
	gst_object_unref(GstData.bus);

	gst_bin_add_many(GST_BIN(GstData.pipeline), GstData.v4l2src, GstData.queue,
					 GstData.h264parse, GstData.flvmux, GstData.rtmpsink,NULL);

	if(gst_element_link_filtered(GstData.v4l2src, GstData.queue, caps_v4l2src) != TRUE)
	{
		g_printerr ("GstData.v4l2src could not link GstData.queue\n");
		gst_object_unref (GstData.pipeline);
		return -1;
	}
	gst_caps_unref (caps_v4l2src);

	if(gst_element_link(GstData.queue, GstData.h264parse) != TRUE)
	{
		g_printerr ("GstData.queue could not link GstData.h264parse\n");
		gst_object_unref (GstData.pipeline);
		return -1;
	}

	if(gst_element_link_filtered(GstData.h264parse, GstData.flvmux, caps_flv_sink) != TRUE)
	{
		g_printerr ("GstData.h264parse could not link GstData.flvmux\n");
		gst_object_unref (GstData.pipeline);
		return -1;
	}
	gst_caps_unref (caps_flv_sink);

	if(gst_element_link(GstData.flvmux, GstData.rtmpsink) != TRUE)
	{
		g_printerr ("GstData.h264parse could not link GstData.flvmux\n");
		gst_object_unref (GstData.pipeline);
		return -1;
	}

	printf("========= link v4l2 rtmp pipeline running ==========\n");
	gst_element_set_state (GstData.pipeline, GST_STATE_PLAYING);
	GstData.loop = g_main_loop_new(NULL, FALSE);	// Create gstreamer loop
	g_main_loop_run(GstData.loop);					// Loop will run until receiving EOS (end-of-stream), will block here
	printf("g_main_loop_run returned, stopping rtmp!\n");
	gst_element_set_state (GstData.pipeline, GST_STATE_NULL);		// Stop pipeline to be released
	printf("Deleting pipeline\n");
	gst_object_unref (GstData.pipeline);							// THis will also delete all pipeline elements
	g_source_remove(GstData.bus_watch_id);
	g_main_loop_unref(GstData.loop);

	return 0;
}

此代码在Ubuntu16.04系统下使用gcc编译,makefile以下:
须要将系统目录下的关于gstreamer的库文件拷贝到当前目录的libs_x86目录下,另外系统的gstreamer库连接文件都带有.so.0后缀,去掉最后的.0,保留到.so便可。web

CFLAGS = -v -g -Wall -Wno-shift-count-overflow -I./include
LDFLAGS = -L./libs_x86
CC = gcc
EXTRA_LIBS = -lstdc++ -lm -lpthread -lgstreamer-1.0 -lgstbase-1.0 -lgobject-2.0 -lgmodule-2.0 -lglib-2.0 -lpcre -lrt
SRC = v4l2_rtmp.c
TARGET = v4l2_rtmp
ALL:
	$(CC) $(CFLAGS) $(LDFLAGS) $(SRC) -o $(TARGET) $(EXTRA_LIBS) 
clean:
	rm v4l2_rtmp *.raw *.mp4 *.wav -rf

此代码只是一个简单实现,并无作其余操做,也没有长时间验证网络的稳定性,只是说明了可行性,另外尚未添加音频,等过几天再把音频也加上来。网络