v4l2读取摄像头数据推送到流媒体服务器(使用RTMPdump)

RTMP是Real Time Messaging Protocol(实时消息传输协议),RTMPdump 封装了RTMP协议的一些接口,使用户使用RTMP协议更加的方便。关于RTMPdump的使用,可以参考博客

linux系统RTMPdump(libRTMP) 通过RTMP 发布H264数据

linux系统RTMPdump(libRTMP) 通过RTMP 发布FLV数据

在进行RTMP实验的时候,需要先搭建好RTMP的编译文件和一个RTMP服务器,具体可以参考:

linux 编译安装TRMPdump(libRTMP)

nginx 搭建rtmp流媒体服务器

要实现RTMP直播V4L2摄像头数据,最简单的方案就是使用FFMPEG,它已经实现了所有的功能,用户只要使用一条命令就行了。但是对于有些嵌入式设备,没有足够的硬件资源来运行FFMPEG,对于这种情况,只能是自己来实现所需的所有接口。本文主要介绍在linux系统,用户通过v4l2采集摄像头的数据,然后对摄像头数据进行x264编码,再使用RTMPdump库通过RTMP协议将编码好的H264数据推送到RTMP服务器,最后客户端从RTMP服务器中将数据拉下来,解码,最后显示出来,进而实现实时直播的目的。主要工作流程图如下:


关于v4l2采集摄像头数据并编码成H264,可以参考:V4L2视频采集与编码——学习目录及总结

主程序代码如下:

/*=============================================================================  
 *     FileName: main.cpp
 *         Desc:  
 *       Author: licaibiao  
 *   LastChange: 2017-05-8   
 * =============================================================================*/  
#include "x264_encoder.h"
#include "v4l2_device.h"
#include "librtmp/log.h"
#include <signal.h>
#include <error.h>
 
int runflag=0;
static void sig_user(int signo){
    if(signo==SIGINT){
    	runflag=0;
        printf("received SIGINT\n");
  }
}
 
void rtmp_push_v4l2(){
	char url[]="rtmp://192.168.0.5:1935/live";
	int fps		= 30;
	int rate	= 333;
	int width	= 640;
	int height	= 480;
	int outSize	= 1024;
 
	int index=0;	
	unsigned int tick = 0;
	unsigned int tick_gap = 1000/fps;
	uint32_t now=0;
	uint32_t last_update=0;
 
	int fd;
	int len = 0;
	uint8_t *cam_buf;
	uint32_t pixelformat;
 
	cam_buf = (uint8_t*)malloc(1024*1024*3);
	memset(cam_buf, 0, 1024*1024*3);
	
	pixelformat = V4L2_PIX_FMT_YUYV;
	
    if(signal(SIGINT,sig_user)==SIG_ERR){
		perror("catch SIGINT err");
	}
 
	fd =open_camera();
	init_camera(fd, width, height);
	start_capture(fd);
		
	RTMP_CreatePublish(url,outSize,1,RTMP_LOGINFO);
	printf("connected \n");
	RTMP_InitVideoParams(width,height,fps, rate, pixelformat,false);
	printf("inited \n");
	runflag=1;
	//runflag=3;
		
	while(runflag){
		if(index!=0){
			RTMP_SendScreenCapture(cam_buf,height,tick, pixelformat, width, height);
			printf("send frame index -- %d\n",index);
		}
		last_update=RTMP_GetTime();
 
		read_frame(fd, cam_buf,&len);
 
		tick +=tick_gap;
		now=RTMP_GetTime();
		
		//usleep((tick_gap-now+last_update)*1000);
		usleep(1000);
		index++;
	}
 
	free(cam_buf);
	stop_capture(fd);
	close_camera_device(fd);
	RTMP_DeletePublish();
}
 
int main(){
	rtmp_push_v4l2();
	return 0;
}
 

我使用的是一个30万像素的摄像头,也就是输出图像尺寸为640*480,它可以支持输出MJPEG 和YUV422 两种数据格式,因为需要进行x264编码,所以我这里设置的是输出YUV422(YUYV)格式。我自己搭建的RTMP服务器所在的地址为:rtmp://192.168.0.5:1935/live。

有几点需要注意:

1.在发送数据的时候,一定需要设置合适的帧率,因为在有些平台,可能编码花费的时间较多,并达不到初始化设置的帧率,这样在显示的时候就会出现问题。 
2.需要客户端先向服务端请求数据,然后再向服务器推送h264数据,否则会出现非常明显的图像延时,大约2~3秒。

工程目录如下:

-bash-4.1# tree
.
├── include
│   ├── librtmp
│   │   ├── amf.h
│   │   ├── bytes.h
│   │   ├── dhgroups.h
│   │   ├── dh.h
│   │   ├── handshake.h
│   │   ├── http.h
│   │   ├── log.h
│   │   ├── rtmp.h
│   │   └── rtmp_sys.h
│   ├── librtmp_send264.h
│   ├── sps_decode.h
│   ├── v4l2_device.h
│   ├── x264_config.h
│   ├── x264_encoder.h
│   └── x264.h
├── lib
│   ├── librtmp.a
│   └── libx264.a
├── librtmp_send264.cpp
├── main.cpp
├── Makefile
├── v4l2_device.cpp
└── x264_encoder.cpp

程序运行如下:

-bash-4.1# ./test 
 
camera driver name is : uvcvideo
camera device name is : UVC Camera (046d:0825)
camera bus information: usb-0000:00:1a.0-1.1
 
support device 1.YUV 4:2:2 (YUYV)
support device 2.MJPEG
 
n_buffer = 4
connected 
x264 [warning]: VBV maxrate specified, but no bufsize, ignored
x264 [info]: using cpu capabilities: MMX2 SSE2Fast SSSE3 SSE4.2 AVX
x264 [info]: profile High 4:2:2, level 3.0, 4:2:2 8-bit
inited 
^Creceived SIGINT
select received SIGINT 
x264 [info]: frame I:1     Avg QP:36.90  size:  3106
x264 [info]: frame P:55    Avg QP:25.29  size:  1070
x264 [info]: mb I  I16..4: 25.9% 72.0%  2.1%
x264 [info]: mb P  I16..4:  3.7%  2.5%  0.0%  P16..4: 20.0%  4.0%  0.6%  0.0%  0.0%    skip:69.1%
x264 [info]: final ratefactor: 24.32
x264 [info]: 8x8 transform intra:47.2% inter:32.7%
x264 [info]: coded y,uvDC,uvAC intra: 18.5% 41.6% 7.7% inter: 2.0% 10.7% 0.0%
x264 [info]: i16 v,h,dc,p: 27% 51% 12% 11%
x264 [info]: i8 v,h,dc,ddl,ddr,vr,hd,vl,hu: 13% 45% 30%  2%  1%  1%  3%  1%  4%
x264 [info]: i4 v,h,dc,ddl,ddr,vr,hd,vl,hu: 20% 54% 12%  2%  2%  2%  4%  1%  4%
x264 [info]: i8c dc,h,v,p: 61% 19% 17%  2%
x264 [info]: Weighted P-Frames: Y:0.0% UV:0.0%
x264 [info]: kb/s:265.47

客户端直接使用VLC播放器,效果如下:
在这里插入图片描述
需要完整工程下载链接:
使用RTMPdump(libRTMP)直播来自v4l2的摄像头数据

作者:li_wen01
原文:https://blog.csdn.net/li_wen01/article/details/71548079

  • 1
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个使用C语言实现在Linux上通过v4l2摄像头到RTMP服务器的完整代码示例: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <unistd.h> #include <sys/ioctl.h> #include <sys/mman.h> #include <linux/videodev2.h> #include <libavformat/avformat.h> #define WIDTH 640 #define HEIGHT 480 #define FRAME_RATE 30 #define BIT_RATE 400000 int main(int argc, char* argv[]) { int fd; struct v4l2_capability cap; struct v4l2_format format; struct v4l2_requestbuffers reqbuf; struct v4l2_buffer buf; void* buffer; AVFormatContext* avformat_ctx = NULL; AVCodecContext* avcodec_ctx = NULL; AVStream* stream = NULL; AVCodec* codec = NULL; AVPacket packet; int frame_count = 0; // 打开摄像头设备 fd = open("/dev/video0", O_RDWR); if (fd < 0) { perror("Failed to open device"); return -1; } // 查询摄像头设备能力 if (ioctl(fd, VIDIOC_QUERYCAP, &cap) < 0) { perror("Failed to query device capabilities"); close(fd); return -1; } // 设置视频格式 format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; format.fmt.pix.width = WIDTH; format.fmt.pix.height = HEIGHT; format.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; format.fmt.pix.field = V4L2_FIELD_NONE; if (ioctl(fd, VIDIOC_S_FMT, &format) < 0) { perror("Failed to set video format"); close(fd); return -1; } // 分配并映射内存用于存储视频帧数据 reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; reqbuf.memory = V4L2_MEMORY_MMAP; reqbuf.count = 1; if (ioctl(fd, VIDIOC_REQBUFS, &reqbuf) < 0) { perror("Failed to request buffers"); close(fd); return -1; } memset(&buf, 0, sizeof(buf)); buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; buf.index = 0; if (ioctl(fd, VIDIOC_QUERYBUF, &buf) < 0) { perror("Failed to query buffer"); close(fd); return -1; } buffer = mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset); if (buffer == MAP_FAILED) { perror("Failed to map buffer"); close(fd); return -1; } // 初始化FFmpeg av_register_all(); avformat_network_init(); // 创建AVFormatContext avformat_ctx = avformat_alloc_context(); if (!avformat_ctx) { fprintf(stderr, "Failed to allocate AVFormatContext\n"); munmap(buffer, buf.length); close(fd); return -1; } // 配置输出格式 avformat_ctx->oformat = av_guess_format("flv", NULL, NULL); if (!avformat_ctx->oformat) { fprintf(stderr, "Failed to guess output format\n"); munmap(buffer, buf.length); close(fd); return -1; } // 打开输出URL if (avio_open2(&avformat_ctx->pb, "rtmp://your-rtmp-server-url", AVIO_FLAG_WRITE, NULL, NULL) < 0) { fprintf(stderr, "Failed to open output URL\n"); munmap(buffer, buf.length); close(fd); return -1; } // 创建视频 stream = avformat_new_stream(avformat_ctx, codec); if (!stream) { fprintf(stderr, "Failed to allocate stream\n"); avio_close(avformat_ctx->pb); avformat_free_context(avformat_ctx); munmap(buffer, buf.length); close(fd); return -1; } avcodec_ctx = stream->codec; avcodec_ctx->codec_id = avformat_ctx->oformat->video_codec; avcodec_ctx->codec_type = AVMEDIA_TYPE_VIDEO; avcodec_ctx->width = WIDTH; avcodec_ctx->height = HEIGHT; avcodec_ctx->bit_rate = BIT_RATE; avcodec_ctx->time_base.num = 1; avcodec_ctx->time_base.den = FRAME_RATE; avcodec_ctx->gop_size = 10; avcodec_ctx->pix_fmt = AV_PIX_FMT_YUV420P; // 初始化视频编码器 if (avcodec_open2(avcodec_ctx, codec, NULL) < 0) { fprintf(stderr, "Failed to open video encoder\n"); avio_close(avformat_ctx->pb); avformat_free_context(avformat_ctx); munmap(buffer, buf.length); close(fd); return -1; } // 写入文件头 if (avformat_write_header(avformat_ctx, NULL) < 0) { fprintf(stderr, "Failed to write header\n"); avcodec_close(avcodec_ctx); avio_close(avformat_ctx->pb); avformat_free_context(avformat_ctx); munmap(buffer, buf.length); close(fd); return -1; } // 开始视频采集 if (ioctl(fd, VIDIOC_STREAMON, &buf.type) < 0) { perror("Failed to start capture"); avcodec_close(avcodec_ctx); avio_close(avformat_ctx->pb); avformat_free_context(avformat_ctx); munmap(buffer, buf.length); close(fd); return -1; } while (frame_count < 100) { // 采集100帧 // 将视频帧数据读取到缓冲区 memset(&buf, 0, sizeof(buf)); buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; buf.index = 0; if (ioctl(fd, VIDIOC_QBUF, &buf) < 0) { perror("Failed to enqueue buffer"); avcodec_close(avcodec_ctx); avio_close(avformat_ctx->pb); avformat_free_context(avformat_ctx); munmap(buffer, buf.length); close(fd); return -1; } if (ioctl(fd, VIDIOC_DQBUF, &buf) < 0) { perror("Failed to dequeue buffer"); avcodec_close(avcodec_ctx); avio_close(avformat_ctx->pb); avformat_free_context(avformat_ctx); munmap(buffer, buf.length); close(fd); return -1; } // 将YUYV格式的视频帧转换为YUV420P格式 uint8_t* yuyv = (uint8_t*)buffer; uint8_t* yuv420p = (uint8_t*)av_malloc(WIDTH * HEIGHT * 3 / 2); for (int i = 0; i < WIDTH * HEIGHT; i++) { int y = yuyv[i * 2]; int u = yuyv[i * 2 + 1]; int v = yuyv[i * 2 + 3]; int r = y + (int)1.402 * (v - 128); int g = y - (int)0.344136 * (u - 128) - (int)0.714136 * (v - 128); int b = y + (int)1.772 * (u - 128); yuv420p[i] = r > 255 ? 255 : (r < 0 ? 0 : r); yuv420p[i + WIDTH * HEIGHT] = g > 255 ? 255 : (g < 0 ? 0 : g); yuv420p[i + WIDTH * HEIGHT + WIDTH * HEIGHT / 4] = b > 255 ? 255 : (b < 0 ? 0 : b); } // 编码并写入视频帧 av_init_packet(&packet); packet.data = NULL; packet.size = 0; AVFrame* frame = av_frame_alloc(); frame->format = avcodec_ctx->pix_fmt; frame->width = avcodec_ctx->width; frame->height = avcodec_ctx->height; av_frame_get_buffer(frame, 0); av_image_fill_arrays(frame->data, frame->linesize, yuv420p, avcodec_ctx->pix_fmt, avcodec_ctx->width, avcodec_ctx->height, 1); if (avcodec_encode_video2(avcodec_ctx, &packet, frame, NULL) < 0) { fprintf(stderr, "Failed to encode video frame\n"); av_frame_free(&frame); av_free(yuv420p); avcodec_close(avcodec_ctx); avio_close(avformat_ctx->pb); avformat_free_context(avformat_ctx); munmap(buffer, buf.length); close(fd); return -1; } av_frame_free(&frame); av_free(yuv420p); packet.stream_index = stream->index; packet.pts = packet.dts = frame_count * (avcodec_ctx->time_base.den) / ((avcodec_ctx->time_base.num) * FRAME_RATE); packet.duration = (avcodec_ctx->time_base.den) / ((avcodec_ctx->time_base.num) * FRAME_RATE); if (av_interleaved_write_frame(avformat_ctx, &packet) < 0) { fprintf(stderr, "Failed to write video frame\n"); av_packet_unref(&packet); avcodec_close(avcodec_ctx); avio_close(avformat_ctx->pb); avformat_free_context(avformat_ctx); munmap(buffer, buf.length); close(fd); return -1; } av_packet_unref(&packet); frame_count++; } // 结束视频采集 if (ioctl(fd, VIDIOC_STREAMOFF, &buf.type) < 0) { perror("Failed to stop capture"); avcodec_close(avcodec_ctx); avio_close(avformat_ctx->pb); avformat_free_context(avformat_ctx); munmap(buffer, buf.length); close(fd); return -1; } // 写入文件尾 if (av_write_trailer(avformat_ctx) < 0) { fprintf(stderr, "Failed to write trailer\n"); avcodec_close(avcodec_ctx); avio_close(avformat_ctx->pb); avformat_free_context(avformat_ctx); munmap(buffer, buf.length); close(fd); return -1; } // 释放资源 munmap(buffer, buf.length); close(fd); avcodec_close(avcodec_ctx); avio_close(avformat_ctx->pb); avformat_free_context(avformat_ctx); return 0; } ``` 请注意,上述代码只是一个示例,并且可能需要根据您的需求进行适当的修改和调整。您需要将`"rtmp://your-rtmp-server-url"`替换为实际的RTMP服务器URL。此外,还需要确保已经安装了libavformat和libavcodec库,并将编译命令中添加相应的链接选项。 编译和运行此代码可能需要一些额外的设置和依赖项,因此请根据您的环境和需求进行适当的调整。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值