18

开发RTSP 直播软件 H264 AAC 编码

 4 years ago
source link: http://www.cnblogs.com/ningci/p/12463198.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

上一篇对摄像头预览,拍照做了大概的介绍,现在已经可以拿到视频帧了,在加上 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

N3e2Uv2.jpg!web

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

Av6z2y3.jpg!web

把 ffmpeg 拆分好的文件,复制到 webroot 目录里面,然后使用 VLC 播放,也可以通过 html5 的 video 在手机上播放,手机上浏览器支持 ts 流比较好

eUrIvib.jpg!web

2,RTSP 流 使用 live555 发布

http://live555.com/liveMedia/public/ 下载源码,编译安装,不得不说有的时候在 ubuntu 上开发的确比 win 上简单。

下载解压后,执行 ./genMakefiles linux 在 make 会生成 mediaServer/live555MediaServer 运行它,在它的目录放一些文件,这里放的是 mkv 的文件,这里还写了支持的其它类型的文件。

然后使用 ffplay 进行播放。

Iz6RJfj.jpg!web

使用 wireshark 抓包查看数据包

IjaEvur.jpg!web

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 播放器打开

eUJnmiZ.gif

实际上这个是动的,不过 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 播放。

IR3uAjn.gif

最终要完成的软件是 windows 版 摄像头直播软件,采用技术方案 MFC 、DirectShow、ffmpeg 、live555 。

原理是 DirectShow 采集 RGB 和 PCM 经过 h264 和 aac 编码以后,送到 live555 通过 RTSP RTP 传输,在理想点就是实现 P2P 以减少服务器压力。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK