内容简介:上一篇对摄像头预览,拍照做了大概的介绍,现在已经可以拿到视频帧了,在加上 RTSP 实现,就是直播的雏形,当然还要加上一些 WEB 管理和手机平台的支援,就是一整套直播软件。介绍一些基础概念:RTP RTSP RTMPRTP 实时传输协议,RTMP 以前 flash 用的视频协议,RTSP 目前比较流行的 直播协议
上一篇对摄像头预览,拍照做了大概的介绍,现在已经可以拿到视频帧了,在加上 RTSP 实现,就是直播的雏形,当然还要加上一些 WEB 管理和手机平台的支援,就是一整套直播软件。
介绍一些基础概念:RTP RTSP RTMP
RTP 实时传输协议,RTMP 以前 flash 用的视频协议,RTSP 目前比较流行的 直播协议
用到的软件和第三方库:ffmpeg live555 VLC
VLC 全平台播放器,win ubuntu mac os android 各个平台都有,功能强大,UI美观,还没有广告。
live555 开源 RTP RTSP 项目
ffmpeg 开源编解码器,多种格式转换,加水印,ffplay 更是全能播放器(就是控制做的不行)
ffplay 在 win 上使用时,需要加一个环境变量,否则没声音 set SDL_AUDIODRIVER=directsound
1,在本机发布一个 ts 流,用 VLC 和 手机浏览器, 进行播放。
ffmpeg -i 1-21.rm -profile:v baseline -level 3.0 -s 640x360 -start_number 0 -hls_time 10 -hls_list_size 0 -f hls 1-12.m3u8
把 ffmpeg 拆分好的文件,复制到 webroot 目录里面,然后使用 VLC 播放,也可以通过 html5 的 video 在手机上播放,手机上浏览器支持 ts 流比较好
2,RTSP 流 使用 live555 发布
http://live555.com/liveMedia/public/ 下载源码,编译安装,不得不说有的时候在 ubuntu 上开发的确比 win 上简单。
下载解压后,执行 ./genMakefiles linux 在 make 会生成 mediaServer/live555MediaServer 运行它,在它的目录放一些文件,这里放的是 mkv 的文件,这里还写了支持的其它类型的文件。
然后使用 ffplay 进行播放。
使用 wireshark 抓包查看数据包
RTSP 文档 https://www.rfc-editor.org/rfc/rfc2326.html 自己对照着看吧,如果完全自己从0开发,这些是需要知道的。
3,H264 ACC 编码
要先找一些原始数据,才能开始编码。直接从 DirectShow 中,的确是可以拿到数据,每次启动什么的还是有点麻烦,所以先生成一些数据,使用 ffmpeg 提取视频为图片
ffmpeg -i 1.mp4 -r 25 -q:v 2 -f image2 image-%5d.jpg
从 1.mp4 中提取了图片,帧率是 25 。
win 平台下载编译好的 lib 比较省心, https://ffmpeg.zeranoe.com/builds/ 下载 ffmpeg-4.2.2-win32-dev.zip
linux 可以自行编译,需要下载很多库 x264 x265 啥的,这样看来,还是 win 省心,直接用现成的 lib 就行了。
参考例子 ffmpeg-4.1/doc/examples$
编译
gcc encode_video.c -lavcodec -lavutil -o encode_video
gcc muxing.c -lavcodec -lavutil -lswscale -lswresample -lavformat -lm -o muxing
执行 ./encode_video 1.mp4 libx264 ./muxing 2.mp4
使用 ffplay 播放器打开
实际上这个是动的,不过 GIF 录的不好。
H264 中要求是 YUV420P 格式,JPG 默认解码 RGB 也可以解码为 JCS_YCbCr 。YCbCr 和 YUV 几种格式的区别,ffmpeg 中有以下几种:
AV_PIX_FMT_YUV444P
AV_PIX_FMT_YUV422P
AV_PIX_FMT_YUV420P
其它 RGB 也有几种 RGB24 - 888 RGB16 - 565
1 /**
2 * author:nejidev
3 * date:2020-03-14 12:32
4 */
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <dirent.h>
8 #include <string.h>
9 #include <jpeglib.h>
10 #include <setjmp.h>
11
12 #include <libavformat/avformat.h>
13 #include <libavcodec/avcodec.h>
14 #include <libswscale/swscale.h>
15
16 #include <libavutil/opt.h>
17 #include <libavutil/imgutils.h>
18 struct my_error_mgr {
19 struct jpeg_error_mgr pub;
20 jmp_buf setjmp_buffer;
21 };
22
23 typedef struct my_error_mgr *my_error_ptr;
24
25 void my_error_exit(j_common_ptr cinfo)
26 {
27 my_error_ptr myerr = (my_error_ptr) cinfo->err;
28 (*cinfo->err->output_message) (cinfo);
29 longjmp(myerr->setjmp_buffer, 1);
30 }
31
32 int read_jpeg(const char *filename, int *out_width, int *out_height, char **out_rgb_buff)
33 {
34 struct jpeg_decompress_struct cinfo;
35 struct my_error_mgr jerr;
36 FILE *infile;
37 char *buffer;
38 char *p;
39 int row_stride;
40
41 if(NULL == (infile = fopen(filename, "rb")))
42 {
43 fprintf(stderr, "can't open %s\n", filename);
44 return 0;
45 }
46
47 cinfo.err = jpeg_std_error(&jerr.pub);
48 jerr.pub.error_exit = my_error_exit;
49 if(setjmp(jerr.setjmp_buffer))
50 {
51 jpeg_destroy_decompress(&cinfo);
52 fclose(infile);
53 return 0;
54 }
55 jpeg_create_decompress(&cinfo);
56 jpeg_stdio_src(&cinfo, infile);
57 (void)jpeg_read_header(&cinfo, TRUE);
58
59 (void) jpeg_start_decompress(&cinfo);
60
61 row_stride = cinfo.output_width * cinfo.output_components;
62 buffer = (char *)malloc(row_stride * cinfo.output_height);
63 memset(buffer, 0, row_stride * cinfo.output_height);
64 *out_rgb_buff = buffer;
65
66 *out_width = cinfo.output_width;
67 *out_height = cinfo.output_height;
68
69 while(cinfo.output_scanline < cinfo.output_height)
70 {
71 p = buffer + cinfo.output_scanline * row_stride;
72 (void) jpeg_read_scanlines(&cinfo, (JSAMPARRAY)&p, 1);
73 }
74
75 (void) jpeg_finish_decompress(&cinfo);
76 jpeg_destroy_decompress(&cinfo);
77 fclose(infile);
78 return 1;
79 }
80
81 static void encode(AVCodecContext *enc_ctx, AVFrame *frame, AVPacket *pkt,
82 FILE *outfile)
83 {
84 int ret;
85
86 /* send the frame to the encoder */
87 if (frame)
88 printf("Send frame %3"PRId64"\n", frame->pts);
89
90 ret = avcodec_send_frame(enc_ctx, frame);
91 if (ret < 0) {
92 fprintf(stderr, "Error sending a frame for encoding\n");
93 exit(1);
94 }
95
96 while (ret >= 0) {
97 ret = avcodec_receive_packet(enc_ctx, pkt);
98 if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
99 return;
100 else if (ret < 0) {
101 fprintf(stderr, "Error during encoding\n");
102 exit(1);
103 }
104
105 printf("Write packet %3"PRId64" (size=%5d)\n", pkt->pts, pkt->size);
106 fwrite(pkt->data, 1, pkt->size, outfile);
107 av_packet_unref(pkt);
108 }
109 }
110
111 int get_video_size(const char *dir_path, int *out_width, int *out_height)
112 {
113 DIR *dir;
114 struct dirent *file;
115 char filename[256];
116 char *image_buff;
117
118 dir = opendir(dir_path);
119 while(NULL != (file = readdir(dir)))
120 {
121 if(0 != strcmp(".", file->d_name) && 0 != strcmp("..", file->d_name))
122 {
123 snprintf(filename, 256, "%s/%s", dir_path, file->d_name);
124 read_jpeg(filename, out_width, out_height, ℑ_buff);
125 free(image_buff);
126 break;
127 }
128 }
129
130 closedir(dir);
131 return 0;
132 }
133
134 int main(int argc, char **argv)
135 {
136 const char *filename, *dir_path, *codec_name = "libx264";
137 const AVCodec *codec;
138 AVCodecContext *c= NULL;
139 int i, ret, x, y;
140 FILE *f;
141 AVFrame *frame;
142 AVPacket *pkt;
143 uint8_t endcode[] = { 0, 0, 1, 0xb7 };
144
145 struct SwsContext *sws_ctx = NULL;
146 int video_width;
147 int video_height;
148 int fps = 25;
149
150 DIR *dir;
151 struct dirent *file;
152 char file_image[256];
153 char *image_buff;
154
155 if (argc <= 2) {
156 fprintf(stderr, "Usage: %s <output file> <image dir>\n", argv[0]);
157 exit(0);
158 }
159 filename = argv[1];
160 dir_path = argv[2];
161
162 if(get_video_size(dir_path, &video_width, &video_height))
163 {
164 fprintf(stderr, "get video size failed\n");
165 exit(1);
166 }
167
168 /* find the mpeg1video encoder */
169 codec = avcodec_find_encoder_by_name(codec_name);
170 if (!codec) {
171 fprintf(stderr, "Codec '%s' not found\n", codec_name);
172 exit(1);
173 }
174
175 c = avcodec_alloc_context3(codec);
176 if (!c) {
177 fprintf(stderr, "Could not allocate video codec context\n");
178 exit(1);
179 }
180
181 pkt = av_packet_alloc();
182 if (!pkt)
183 exit(1);
184
185 /* put sample parameters */
186 c->bit_rate = 400000;
187 /* resolution must be a multiple of two */
188 c->width = video_width;
189 c->height = video_height;
190 /* frames per second */
191 c->time_base = (AVRational){1, fps};
192 c->framerate = (AVRational){fps, 1};
193
194 /* emit one intra frame every ten frames
195 * check frame pict_type before passing frame
196 * to encoder, if frame->pict_type is AV_PICTURE_TYPE_I
197 * then gop_size is ignored and the output of encoder
198 * will always be I frame irrespective to gop_size
199 */
200 c->gop_size = 10;
201 c->max_b_frames = 0;
202 c->pix_fmt = AV_PIX_FMT_YUV420P;
203
204 if (codec->id == AV_CODEC_ID_H264)
205 av_opt_set(c->priv_data, "preset", "slow", 0);
206
207 /* open it */
208 ret = avcodec_open2(c, codec, NULL);
209 if (ret < 0) {
210 fprintf(stderr, "Could not open codec: %s\n", av_err2str(ret));
211 exit(1);
212 }
213
214 f = fopen(filename, "wb");
215 if (!f) {
216 fprintf(stderr, "Could not open %s\n", filename);
217 exit(1);
218 }
219
220 frame = av_frame_alloc();
221 if (!frame) {
222 fprintf(stderr, "Could not allocate video frame\n");
223 exit(1);
224 }
225 frame->format = c->pix_fmt;
226 frame->width = c->width;
227 frame->height = c->height;
228
229 ret = av_frame_get_buffer(frame, 32);
230 if (ret < 0) {
231 fprintf(stderr, "Could not allocate the video frame data\n");
232 exit(1);
233 }
234
235 //rgb24 to yun420p
236 sws_ctx = sws_getContext(frame->width, frame->height, AV_PIX_FMT_BGR24,
237 frame->width, frame->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC,
238 NULL,NULL,NULL);
239
240 struct dirent **namelist;
241 int n;
242
243 n = scandir(dir_path, &namelist, NULL, alphasort);
244 for(i = 0; i < n; i++)
245 {
246 if(0 != strcmp(".", namelist[i]->d_name) && 0 != strcmp("..", namelist[i]->d_name))
247 {
248 snprintf(file_image, sizeof(file_image), "%s/%s", dir_path, namelist[i]->d_name);
249
250 printf("file_image:%s\n", file_image);
251 read_jpeg(file_image, &video_width, &video_height, ℑ_buff);
252
253 uint8_t *indata[AV_NUM_DATA_POINTERS] = { 0 };
254 indata[0] = image_buff;
255 int inlinesize[AV_NUM_DATA_POINTERS] = { 0 };
256 inlinesize[0] = frame->width * 3;
257
258 ret = sws_scale(sws_ctx, indata, inlinesize, 0, frame->height, frame->data, frame->linesize);
259
260 /* make sure the frame data is writable */
261 ret = av_frame_make_writable(frame);
262 if (ret < 0) exit(1);
263
264 frame->pts = i;
265
266 /* encode the image */
267 encode(c, frame, pkt, f);
268
269 free(image_buff);
270 }
271 free(namelist[i]);
272 }
273 free(namelist);
274 sws_freeContext(sws_ctx);
275
276 closedir(dir);
277
278 /* flush the encoder */
279 encode(c, NULL, pkt, f);
280
281 /* add sequence end code to have a real MPEG file */
282 fwrite(endcode, 1, sizeof(endcode), f);
283 fclose(f);
284
285 avcodec_free_context(&c);
286 av_frame_free(&frame);
287 av_packet_free(&pkt);
288
289 return 0;
290 }
这个是 把 上面拆分的 jpg 图片合成 264 编码,编译方式:gcc encode_video_h264.c -lavcodec -lavutil -lswscale -lswresample -lavformat -ljpeg
这里使用读取文件夹内的所有 jpg ,read_jpeg() 是一个用 libjpeg 实现的,得到 jpeg 解码 RGB 数据的方法,但是 编码器需要 YUV420P 所以使用 sws_scale 进行转换。
将转换好的 xin.264 文件复制到 mediaServer 下面,启动 live555MediaServer 用 VLC 播放。
最终要完成的软件是 windows 版 摄像头直播软件,采用技术方案 MFC 、DirectShow、ffmpeg 、live555 。
原理是 DirectShow 采集 RGB 和 PCM 经过 h264 和 aac 编码以后,送到 live555 通过 RTSP RTP 传输,在理想点就是实现 P2P 以减少服务器压力。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 网络爬虫直播观看总结(掘金直播第十期)
- 一文盘点直播技术中的编解码、直播协议、网络传输与简单实现
- 一文盘点直播技术中的编解码、直播协议、网络传输与简单实现
- 直播与 RTC 融合
- 直播协议
- 熊猫直播技术架构演进
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
C# 6.0本质论
[美] Mark Michaelis(马克·米凯利斯)、[美] Eric Lippert(埃里克·利珀特) / 周靖、庞燕 / 人民邮电出版社 / 2017-2-1 / 108
这是C#领域中一部广受好评的名作,作者用一种易于理解的方式详细介绍了C#语言的各个方面。全书共有21章和4个附录(其中哟2个附录从网上下载),介绍了C#语言的数据类型、操作符、方法、类、接口、异常处理等基本概念,深入讨论了泛型、迭代器、反射、线程和互操作性等高级主题,还介绍了LINQ技术,以及与其相关的扩展方法、分部方法、Lambda表达式、标准查询操作符和查询表达式等内容。每章开头的“思维导图”......一起来看看 《C# 6.0本质论》 这本书的介绍吧!