开发RTSP 直播软件 H264 AAC 编码
作者:互联网
上一篇对摄像头预览,拍照做了大概的介绍,现在已经可以拿到视频帧了,在加上 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, &image_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, &image_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 以减少服务器压力。
标签:AAC,H264,frame,RTSP,cinfo,height,video,ret,jpeg 来源: https://www.cnblogs.com/ningci/p/12463198.html