![](/style/images/good.png)
![](/style/images/bad.png)
开发RTSP 直播软件 H264 AAC 编码
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
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 以减少服务器压力。
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK