public void StartPlay(NetworkStream stream) { try { AVCodec *mCodec = ffmpeg.avcodec_find_decoder(AVCodecID.AV_CODEC_ID_H264); if (mCodec == null) { Console.WriteLine("can not find h264 decoder"); return; } AVCodecContext *mCodecContext = ffmpeg.avcodec_alloc_context3(mCodec); if (mCodecContext == null) { Console.WriteLine("can not allcote codec context"); return; } if (ffmpeg.avcodec_open2(mCodecContext, mCodec, null) < 0) { Console.WriteLine("can not open codec"); ffmpeg.avcodec_free_context(&mCodecContext); return; } AVCodecParserContext *parser = ffmpeg.av_parser_init((int)AVCodecID.AV_CODEC_ID_H264); if (parser == null) { Console.WriteLine("can not initialize parser"); ffmpeg.avcodec_close(mCodecContext); ffmpeg.avcodec_free_context(&mCodecContext); return; } //AVFrame用于存储解码后的像素数据(YUV) //内存分配 AVFrame *pFrame = ffmpeg.av_frame_alloc(); //YUV420 AVFrame *pFrameYUV = ffmpeg.av_frame_alloc(); //只有指定了AVFrame的像素格式、画面大小才能真正分配内存 //缓冲区分配内存 int out_buffer_size = ffmpeg.avpicture_get_size(AVPixelFormat.AV_PIX_FMT_YUV420P, 720, 1280); byte *out_buffer = (byte *)ffmpeg.av_malloc((ulong)out_buffer_size); //初始化缓冲区 ffmpeg.avpicture_fill((AVPicture *)pFrameYUV, out_buffer, AVPixelFormat.AV_PIX_FMT_YUV420P, 720, 1280); //用于转码(缩放)的参数,转之前的宽高,转之后的宽高,格式等 //SwsContext* sws_ctx = ffmpeg.sws_getContext(mCodecContext->width, mCodecContext->height, AVPixelFormat.AV_PIX_FMT_YUV420P /*pCodecCtx->pix_fmt*/, mCodecContext->width, mCodecContext->height, AVPixelFormat.AV_PIX_FMT_YUV420P, ffmpeg.SWS_BICUBIC, null, null, null); SwsContext *sws_ctx = ffmpeg.sws_getContext(720, 1280, AVPixelFormat.AV_PIX_FMT_YUV420P /*pCodecCtx->pix_fmt*/, 720, 1280, AVPixelFormat.AV_PIX_FMT_YUV420P, ffmpeg.SWS_BICUBIC, null, null, null); AVPacket packet; ffmpeg.av_init_packet(&packet); byte[] inbuffer = new byte[4096]; int cur_size, ret, got_picture; int readLen = 0; bool first_time = true; sdlvideo.SDL_Init(720, 1280); while (true) { readLen = 0; cur_size = stream.Read(inbuffer, 0, 4096); if (cur_size <= 0) { Console.WriteLine("read eof"); break; } while (cur_size > 0) { fixed(byte *cur_ptr = inbuffer) { int len = ffmpeg.av_parser_parse2(parser, mCodecContext, &packet.data, &packet.size, cur_ptr + readLen, cur_size, ffmpeg.AV_NOPTS_VALUE, ffmpeg.AV_NOPTS_VALUE, -1); readLen += len; cur_size -= len; } if (packet.size == 0) { continue; } Console.WriteLine($"[Packet]Size:{packet.size}"); ret = ffmpeg.avcodec_decode_video2(mCodecContext, pFrame, &got_picture, &packet); if (ret < 0) { Console.WriteLine("Decode Error.\n"); break; } // 读取解码后的帧数据 if (got_picture > 0) { if (first_time) { Console.WriteLine($"width:{mCodecContext->width}\nheight:{mCodecContext->height}\n\n"); first_time = false; } //AVFrame转为像素格式YUV420,宽高 ffmpeg.sws_scale(sws_ctx, pFrame->data, pFrame->linesize, 0, mCodecContext->height, pFrameYUV->data, pFrameYUV->linesize); //SDL播放YUV数据 var data = out_buffer; sdlvideo.SDL_Display(mCodecContext->width, mCodecContext->height, (IntPtr)data, out_buffer_size, pFrameYUV->linesize[0]); } } } Console.WriteLine("exit loop"); ffmpeg.av_parser_close(parser); ffmpeg.avcodec_close(mCodecContext); ffmpeg.avcodec_free_context(&mCodecContext); // todo notify stopped } catch (Exception ex) { Console.WriteLine("H264SocketParser.thread error:", ex); } }
/// <summary> /// 视频H264转YUV并使用SDL进行播放 /// </summary> /// <param name="fileName"></param> /// <param name="sdlVideo"></param> /// <returns></returns> public unsafe int RunVideo(string fileName, SDLHelper sdlVideo) { IsRun = true; exit_thread = false; pause_thread = false; threadVideo = Thread.CurrentThread; int error, frame_count = 0; int got_picture, ret; SwsContext * pSwsCtx = null; AVFormatContext *ofmt_ctx = null; IntPtr convertedFrameBufferPtr = IntPtr.Zero; try { // 注册编解码器 ffmpeg.avcodec_register_all(); // 获取文件信息上下文初始化 ofmt_ctx = ffmpeg.avformat_alloc_context(); // 打开媒体文件 error = ffmpeg.avformat_open_input(&ofmt_ctx, fileName, null, null); if (error != 0) { throw new ApplicationException($"ffmpeg avformat_open_input error {error}"); } // 获取流的通道 for (int i = 0; i < ofmt_ctx->nb_streams; i++) { if (ofmt_ctx->streams[i]->codec->codec_type == AVMediaType.AVMEDIA_TYPE_VIDEO) { videoindex = i; Console.WriteLine("video.............." + videoindex); } } if (videoindex == -1) { Console.WriteLine("Couldn't find a video stream.(没有找到视频流)"); return(-1); } // 视频流处理 if (videoindex > -1) { //获取视频流中的编解码上下文 AVCodecContext *pCodecCtx = ofmt_ctx->streams[videoindex]->codec; //根据编解码上下文中的编码id查找对应的解码 AVCodec *pCodec = ffmpeg.avcodec_find_decoder(pCodecCtx->codec_id); if (pCodec == null) { Console.WriteLine("没有找到编码器"); return(-1); } //打开编码器 if (ffmpeg.avcodec_open2(pCodecCtx, pCodec, null) < 0) { Console.WriteLine("编码器无法打开"); return(-1); } Console.WriteLine("Find a video stream.channel=" + videoindex); //输出视频信息 var format = ofmt_ctx->iformat->name->ToString(); var len = (ofmt_ctx->duration) / 1000000; var width = pCodecCtx->width; var height = pCodecCtx->height; Console.WriteLine("video format:" + format); Console.WriteLine("video length:" + len); Console.WriteLine("video width&height:width=" + width + " height=" + height); Console.WriteLine("video codec name:" + pCodec->name->ToString()); //准备读取 //AVPacket用于存储一帧一帧的压缩数据(H264) //缓冲区,开辟空间 AVPacket *packet = (AVPacket *)ffmpeg.av_malloc((ulong)sizeof(AVPacket)); //AVFrame用于存储解码后的像素数据(YUV) //内存分配 AVFrame *pFrame = ffmpeg.av_frame_alloc(); //YUV420 AVFrame *pFrameYUV = ffmpeg.av_frame_alloc(); //只有指定了AVFrame的像素格式、画面大小才能真正分配内存 //缓冲区分配内存 int out_buffer_size = ffmpeg.avpicture_get_size(AVPixelFormat.AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height); byte *out_buffer = (byte *)ffmpeg.av_malloc((ulong)out_buffer_size); //初始化缓冲区 ffmpeg.avpicture_fill((AVPicture *)pFrameYUV, out_buffer, AVPixelFormat.AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height); //用于转码(缩放)的参数,转之前的宽高,转之后的宽高,格式等 SwsContext *sws_ctx = ffmpeg.sws_getContext(pCodecCtx->width, pCodecCtx->height, AVPixelFormat.AV_PIX_FMT_YUV420P /*pCodecCtx->pix_fmt*/, pCodecCtx->width, pCodecCtx->height, AVPixelFormat.AV_PIX_FMT_YUV420P, ffmpeg.SWS_BICUBIC, null, null, null); // 初始化sdl sdlVideo.SDL_Init(pCodecCtx->width, pCodecCtx->height); while (ffmpeg.av_read_frame(ofmt_ctx, packet) >= 0) { // 退出线程 if (exit_thread) { break; } // 暂停解析 if (pause_thread) { while (pause_thread) { Thread.Sleep(100); } } //只要视频压缩数据(根据流的索引位置判断) if (packet->stream_index == videoindex) { //解码一帧视频压缩数据,得到视频像素数据 ret = ffmpeg.avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet); if (ret < 0) { Console.WriteLine("视频解码错误"); return(-1); } // 读取解码后的帧数据 if (got_picture > 0) { frame_count++; Console.WriteLine("视频帧数:第 " + frame_count + " 帧"); //AVFrame转为像素格式YUV420,宽高 ffmpeg.sws_scale(sws_ctx, pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize); //SDL播放YUV数据 var data = out_buffer; sdlVideo.SDL_Display(pCodecCtx->width, pCodecCtx->height, (IntPtr)data, out_buffer_size, pFrameYUV->linesize[0]); } } //释放资源 ffmpeg.av_free_packet(packet); } } } catch (Exception ex) { Console.WriteLine(ex); } finally { if (&ofmt_ctx != null) { ffmpeg.avformat_close_input(&ofmt_ctx);//关闭流文件 } } IsRun = false; return(0); }