关键词搜索

源码搜索 ×
×

C# 使用SDL2实现Mp4文件播放音视频

发布2018-03-08浏览6863次

详情内容

播放音视频的关键:视频的格式是H264,音频的格式是AAC。使用ffmpeg探测流的方式来实现音视频流的解码播放。

数据处理逻辑:H264->YUV     AAC->PCM。

SDL2工具类

  1. using SDL2;
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using System.Runtime.InteropServices;
  6. using System.Text;
  7. using System.Threading.Tasks;
  8. namespace CvNetVideo
  9. {
  10. public unsafe class SDLHelper
  11. {
  12. private IntPtr screen;
  13. private IntPtr sdlrenderer;
  14. private IntPtr sdltexture;
  15. SDL.SDL_Rect sdlrect;
  16. SDL.SDL_Event sdlevent;
  17. bool isInit = false;
  18. public SDLHelper()
  19. {
  20. }
  21. public void SDL_MaximizeWindow()
  22. {
  23. }
  24. public int SDL_Init(int width, int height, IntPtr intPtr)
  25. {
  26. lock (this)
  27. {
  28. if (!isInit)
  29. {
  30. // 初始化调用SDL.SDL_Init(SDL.SDL_INIT_VIDEO | SDL.SDL_INIT_AUDIO | SDL.SDL_INIT_TIMER)
  31. if (SDL.SDL_Init(SDL.SDL_INIT_VIDEO | SDL.SDL_INIT_AUDIO | SDL.SDL_INIT_TIMER) < 0)
  32. {
  33. Console.WriteLine("Could not initialize SDL - {0}\n", SDL.SDL_GetError());
  34. return -1;
  35. }
  36. isInit = true;
  37. }
  38. #region SDL调用
  39. if (sdltexture != IntPtr.Zero)
  40. {
  41. SDL.SDL_DestroyTexture(sdltexture);
  42. }
  43. if (sdlrenderer != IntPtr.Zero)
  44. {
  45. SDL.SDL_DestroyRenderer(sdlrenderer);
  46. }
  47. if (screen != IntPtr.Zero)
  48. {
  49. SDL.SDL_DestroyWindow(screen);
  50. SDL.SDL_RaiseWindow(screen);
  51. SDL.SDL_RestoreWindow(screen);
  52. }
  53. //创建显示窗口
  54. screen = SDL.SDL_CreateWindowFrom(intPtr);
  55. SDL.SDL_ShowWindow(screen);
  56. SDL.SDL_SetWindowSize(screen, width, height);
  57. //screen = SDL.SDL_CreateWindow("SDL EVENT TEST", SDL.SDL_WINDOWPOS_UNDEFINED, SDL.SDL_WINDOWPOS_UNDEFINED, width, height, SDL.SDL_WindowFlags.SDL_WINDOW_OPENGL | SDL.SDL_WindowFlags.SDL_WINDOW_RESIZABLE);
  58. //screen = SDL.SDL_CreateWindow("SDL EVENT TEST", SDL.SDL_WINDOWPOS_UNDEFINED, SDL.SDL_WINDOWPOS_UNDEFINED, screen_w, screen_h, SDL.SDL_WindowFlags.SDL_WINDOW_OPENGL | SDL.SDL_WindowFlags.SDL_WINDOW_RESIZABLE);
  59. if (screen == IntPtr.Zero)
  60. {
  61. Console.WriteLine("Can't creat a window:{0}\n", SDL.SDL_GetError());
  62. return -1;
  63. }
  64. //创建渲染器
  65. sdlrenderer = SDL.SDL_CreateRenderer(screen, -1, SDL.SDL_RendererFlags.SDL_RENDERER_ACCELERATED);
  66. //创建纹理
  67. sdltexture = SDL.SDL_CreateTexture(sdlrenderer, SDL.SDL_PIXELFORMAT_IYUV, (int)SDL.SDL_TextureAccess.SDL_TEXTUREACCESS_STREAMING, width, height);
  68. #endregion
  69. return 0;
  70. }
  71. }
  72. public int SDL_Display(int width, int height, IntPtr pixels, int pixelsSize,
  73. int pitch)
  74. {
  75. lock (this)
  76. {
  77. #region SDL 视频数据渲染播放
  78. //设置纹理的数据
  79. sdlrect.x = 0;
  80. sdlrect.y = 0;
  81. sdlrect.w = width;
  82. sdlrect.h = height;
  83. //SDL.SDL_UpdateTexture(sdltexture, ref sdlrect, pixels, pitch);
  84. SDL.SDL_UpdateTexture(sdltexture, IntPtr.Zero, pixels, pitch);
  85. //复制纹理信息到渲染器目标
  86. SDL.SDL_RenderClear(sdltexture);
  87. //SDL.SDL_Rect srcRect = sdlrect;
  88. //SDL.SDL_RenderCopy(sdlrenderer, sdltexture, ref srcRect, ref sdlrect);
  89. SDL.SDL_RenderCopy(sdlrenderer, sdltexture, IntPtr.Zero, IntPtr.Zero);
  90. //视频渲染显示
  91. SDL.SDL_RenderPresent(sdlrenderer);
  92. return 0;
  93. }
  94. #endregion
  95. }
  96. }
  97. public unsafe class SDLAudio
  98. {
  99. class aa
  100. {
  101. public byte[] pcm;
  102. public int len;
  103. }
  104. int lastIndex = 0;
  105. private List<aa> data = new List<aa>();
  106. //private List<byte> data = new List<byte>();
  107. SDL.SDL_AudioCallback Callback;
  108. public void PlayAudio(IntPtr pcm, int len)
  109. {
  110. lock (this)
  111. {
  112. byte[] bts = new byte[len];
  113. Marshal.Copy(pcm, bts, 0, len);
  114. data.Add(new aa
  115. {
  116. len = len,
  117. pcm = bts
  118. });
  119. }
  120. //SDL.SDL_Delay(10);
  121. }
  122. void SDL_AudioCallback(IntPtr userdata, IntPtr stream, int len)
  123. {
  124. SDL 2.0
  125. SDL.SDL_RWFromMem(stream, 0, len);
  126. //if (audio_len == 0)
  127. // return;
  128. //len = (len > audio_len ? audio_len : len);
  129. if (data.Count == 0)
  130. {
  131. for (int i = 0; i < len; i++)
  132. {
  133. ((byte*)stream)[i] = 0;
  134. }
  135. return;
  136. }
  137. for (int i = 0; i < len; i++)
  138. {
  139. if (data[0].len > i)
  140. {
  141. ((byte*)stream)[i] = data[0].pcm[i];
  142. }
  143. else
  144. ((byte*)stream)[i] = 0;
  145. }
  146. data.RemoveAt(0);
  147. }
  148. public int SDL_Init()
  149. {
  150. Callback = SDL_AudioCallback;
  151. #region SDL调用
  152. 初始化调用SDL.SDL_Init(SDL.SDL_INIT_VIDEO | SDL.SDL_INIT_AUDIO | SDL.SDL_INIT_TIMER)
  153. //if (SDL.SDL_Init(SDL.SDL_INIT_VIDEO | SDL.SDL_INIT_AUDIO | SDL.SDL_INIT_TIMER) < 0)
  154. //{
  155. // Console.WriteLine("Could not initialize SDL - {0}\n", SDL.SDL_GetError());
  156. // return -1;
  157. //}
  158. #endregion
  159. SDL.SDL_AudioSpec wanted_spec = new SDL.SDL_AudioSpec();
  160. wanted_spec.freq = 8000;
  161. wanted_spec.format = SDL.AUDIO_S16;
  162. wanted_spec.channels = 1;
  163. wanted_spec.silence = 0;
  164. wanted_spec.samples = 320;
  165. wanted_spec.callback = Callback;
  166. if (SDL.SDL_OpenAudio(ref wanted_spec, IntPtr.Zero) < 0)
  167. {
  168. Console.WriteLine("can't open audio.");
  169. return -1;
  170. }
  171. //Play
  172. SDL.SDL_PauseAudio(0);
  173. return 0;
  174. }
  175. }
  176. }
SDL实现了基础的播放功能。

C# Mp4文件音视频编码器类

  1. using CV.Video.Base;
  2. using CV.Video.Base.FFmpeg;
  3. using FFmpeg.AutoGen;
  4. using JX;
  5. using System;
  6. using System.Collections.Generic;
  7. using System.Linq;
  8. using System.Runtime.InteropServices;
  9. using System.Text;
  10. using System.Threading;
  11. using System.Threading.Tasks;
  12. namespace CvNetVideo.Codec.Video
  13. {
  14. public unsafe class JT1078CodecForMp4
  15. {
  16. /// <summary>
  17. /// 指示当前解码是否在运行
  18. /// </summary>
  19. public bool IsRun { get; protected set; }
  20. /// <summary>
  21. /// 视频线程
  22. /// </summary>
  23. private Thread threadVideo;
  24. /// <summary>
  25. /// 音频线程
  26. /// </summary>
  27. private Thread threadAudio;
  28. /// <summary>
  29. /// 退出控制
  30. /// </summary>
  31. private bool exit_thread = false;
  32. /// <summary>
  33. /// 暂停控制
  34. /// </summary>
  35. private bool pause_thread = false;
  36. /// <summary>
  37. /// 视频输出流videoindex
  38. /// </summary>
  39. private int videoindex = -1;
  40. /// <summary>
  41. /// 音频输出流audioindex
  42. /// </summary>
  43. private int audioindex = -1;
  44. /// <summary>
  45. /// 视频H264转YUV并使用SDL进行播放
  46. /// </summary>
  47. /// <param name="fileName"></param>
  48. /// <param name="sdlVideo"></param>
  49. /// <returns></returns>
  50. public unsafe int RunVideo(string fileName,SDLHelper sdlVideo)
  51. {
  52. IsRun = true;
  53. exit_thread = false;
  54. pause_thread = false;
  55. threadVideo = Thread.CurrentThread;
  56. int error, frame_count = 0;
  57. int got_picture, ret;
  58. SwsContext* pSwsCtx = null;
  59. AVFormatContext* ofmt_ctx = null;
  60. IntPtr convertedFrameBufferPtr = IntPtr.Zero;
  61. try
  62. {
  63. // 注册编解码器
  64. ffmpeg.avcodec_register_all();
  65. // 获取文件信息上下文初始化
  66. ofmt_ctx = ffmpeg.avformat_alloc_context();
  67. // 打开媒体文件
  68. error = ffmpeg.avformat_open_input(&ofmt_ctx, fileName, null, null);
  69. if (error != 0)
  70. {
  71. throw new ApplicationException(FFmpegBinariesHelper.GetErrorMessage(error));
  72. }
  73. // 获取流的通道
  74. for (int i = 0; i < ofmt_ctx->nb_streams; i++)
  75. {
  76. if (ofmt_ctx->streams[i]->codec->codec_type == AVMediaType.AVMEDIA_TYPE_VIDEO)
  77. {
  78. videoindex = i;
  79. Console.WriteLine("video.............."+videoindex);
  80. }
  81. }
  82. if (videoindex == -1)
  83. {
  84. Console.WriteLine("Couldn't find a video stream.(没有找到视频流)");
  85. return -1;
  86. }
  87. // 视频流处理
  88. if (videoindex > -1)
  89. {
  90. //获取视频流中的编解码上下文
  91. AVCodecContext* pCodecCtx = ofmt_ctx->streams[videoindex]->codec;
  92. //根据编解码上下文中的编码id查找对应的解码
  93. AVCodec* pCodec = ffmpeg.avcodec_find_decoder(pCodecCtx->codec_id);
  94. if (pCodec == null)
  95. {
  96. Console.WriteLine("没有找到编码器");
  97. return -1;
  98. }
  99. //打开编码器
  100. if (ffmpeg.avcodec_open2(pCodecCtx, pCodec, null) < 0)
  101. {
  102. Console.WriteLine("编码器无法打开");
  103. return -1;
  104. }
  105. Console.WriteLine("Find a video stream.channel=" + videoindex);
  106. //输出视频信息
  107. var format = ofmt_ctx->iformat->name->ToString();
  108. var len = (ofmt_ctx->duration) / 1000000;
  109. var width = pCodecCtx->width;
  110. var height = pCodecCtx->height;
  111. Console.WriteLine("video format:" + format);
  112. Console.WriteLine("video length:" + len);
  113. Console.WriteLine("video width&height:width=" + width + " height=" + height);
  114. Console.WriteLine("video codec name:" + pCodec->name->ToString());
  115. //准备读取
  116. //AVPacket用于存储一帧一帧的压缩数据(H264)
  117. //缓冲区,开辟空间
  118. AVPacket* packet = (AVPacket*)ffmpeg.av_malloc((ulong)sizeof(AVPacket));
  119. //AVFrame用于存储解码后的像素数据(YUV)
  120. //内存分配
  121. AVFrame* pFrame = ffmpeg.av_frame_alloc();
  122. //YUV420
  123. AVFrame* pFrameYUV = ffmpeg.av_frame_alloc();
  124. //只有指定了AVFrame的像素格式、画面大小才能真正分配内存
  125. //缓冲区分配内存
  126. int out_buffer_size = ffmpeg.avpicture_get_size(AVPixelFormat.AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height);
  127. byte* out_buffer = (byte*)ffmpeg.av_malloc((ulong)out_buffer_size);
  128. //初始化缓冲区
  129. ffmpeg.avpicture_fill((AVPicture*)pFrameYUV, out_buffer, AVPixelFormat.AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height);
  130. //用于转码(缩放)的参数,转之前的宽高,转之后的宽高,格式等
  131. 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);
  132. while (ffmpeg.av_read_frame(ofmt_ctx, packet) >= 0)
  133. {
  134. // 退出线程
  135. if (exit_thread)
  136. {
  137. break;
  138. }
  139. // 暂停解析
  140. if (pause_thread)
  141. {
  142. while (pause_thread)
  143. {
  144. Thread.Sleep(100);
  145. }
  146. }
  147. //只要视频压缩数据(根据流的索引位置判断)
  148. if (packet->stream_index == videoindex)
  149. {
  150. //解码一帧视频压缩数据,得到视频像素数据
  151. ret = ffmpeg.avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);
  152. if (ret < 0)
  153. {
  154. Console.WriteLine("视频解码错误");
  155. return -1;
  156. }
  157. // 读取解码后的帧数据
  158. if (got_picture>0)
  159. {
  160. frame_count++;
  161. Console.WriteLine("视频帧数:第 " + frame_count + " 帧");
  162. //AVFrame转为像素格式YUV420,宽高
  163. ffmpeg.sws_scale(sws_ctx, pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize);
  164. //SDL播放YUV数据
  165. var data = out_buffer;
  166. sdlVideo.SDL_Display(pCodecCtx->width, pCodecCtx->height, (IntPtr)data, out_buffer_size, pFrameYUV->linesize[0]);
  167. }
  168. }
  169. //释放资源
  170. ffmpeg.av_free_packet(packet);
  171. }
  172. }
  173. }
  174. catch (Exception ex)
  175. {
  176. Console.WriteLine(ex);
  177. }
  178. finally
  179. {
  180. if (&ofmt_ctx != null)
  181. {
  182. ffmpeg.avformat_close_input(&ofmt_ctx);//关闭流文件
  183. }
  184. }
  185. IsRun = false;
  186. return 0;
  187. }
  188. /// <summary>
  189. /// 音频AAC转PCM并使用SDL进行播放
  190. /// </summary>
  191. /// <param name="fileName"></param>
  192. /// <param name="sdlAudio"></param>
  193. /// <returns></returns>
  194. public unsafe int RunAudio(string fileName, SDLAudio sdlAudio)
  195. {
  196. IsRun = true;
  197. exit_thread = false;
  198. pause_thread = false;
  199. threadAudio = Thread.CurrentThread;
  200. int error, frame_count = 0;
  201. int got_frame, ret;
  202. AVFormatContext* ofmt_ctx = null;
  203. SwsContext* pSwsCtx = null;
  204. IntPtr convertedFrameBufferPtr = IntPtr.Zero;
  205. try
  206. {
  207. // 注册编解码器
  208. ffmpeg.avcodec_register_all();
  209. // 获取文件信息上下文初始化
  210. ofmt_ctx = ffmpeg.avformat_alloc_context();
  211. // 打开媒体文件
  212. error = ffmpeg.avformat_open_input(&ofmt_ctx, fileName, null, null);
  213. if (error != 0)
  214. {
  215. throw new ApplicationException(FFmpegBinariesHelper.GetErrorMessage(error));
  216. }
  217. // 获取流的通道
  218. for (int i = 0; i < ofmt_ctx->nb_streams; i++)
  219. {
  220. if (ofmt_ctx->streams[i]->codec->codec_type == AVMediaType.AVMEDIA_TYPE_AUDIO)
  221. {
  222. audioindex = i;
  223. Console.WriteLine("audio.............." + audioindex);
  224. }
  225. }
  226. if (audioindex == -1)
  227. {
  228. Console.WriteLine("Couldn't find a audio stream.(没有找到音频流)");
  229. return -1;
  230. }
  231. // 音频流处理
  232. if (audioindex > -1)
  233. {
  234. //根据索引拿到对应的流,根据流拿到解码器上下文
  235. AVCodecContext* pCodeCtx = ofmt_ctx->streams[audioindex]->codec;
  236. //再根据上下文拿到编解码id,通过该id拿到解码器
  237. AVCodec* pCodec = ffmpeg.avcodec_find_decoder(pCodeCtx->codec_id);
  238. if (pCodec == null)
  239. {
  240. Console.WriteLine("没有找到编码器");
  241. return -1;
  242. }
  243. //打开编码器
  244. if (ffmpeg.avcodec_open2(pCodeCtx,pCodec, null)<0)
  245. {
  246. Console.WriteLine("编码器无法打开");
  247. return -1;
  248. }
  249. Console.WriteLine("Find a audio stream. channel=" + audioindex);
  250. //编码数据
  251. AVPacket* packet = (AVPacket*)ffmpeg.av_malloc((ulong)(sizeof(AVPacket)));
  252. //解压缩数据
  253. AVFrame* frame = ffmpeg.av_frame_alloc();
  254. //frame->16bit 44100 PCM 统一音频采样格式与采样率
  255. SwrContext* swrCtx = ffmpeg.swr_alloc();
  256. //重采样设置选项-----------------------------------------------------------start
  257. //输入的采样格式
  258. AVSampleFormat in_sample_fmt = pCodeCtx->sample_fmt;
  259. //输出的采样格式 16bit PCM
  260. AVSampleFormat out_sample_fmt = AVSampleFormat.AV_SAMPLE_FMT_S16;
  261. //输入的采样率
  262. int in_sample_rate = pCodeCtx->sample_rate;
  263. //输出的采样率
  264. int out_sample_rate = 44100;
  265. //输入的声道布局
  266. long in_ch_layout = (long)pCodeCtx->channel_layout;
  267. //输出的声道布局
  268. int out_ch_layout = ffmpeg.AV_CH_LAYOUT_MONO;
  269. ffmpeg.swr_alloc_set_opts(swrCtx, out_ch_layout, out_sample_fmt, out_sample_rate, in_ch_layout, in_sample_fmt, in_sample_rate, 0, null);
  270. ffmpeg.swr_init(swrCtx);
  271. //重采样设置选项-----------------------------------------------------------end
  272. //获取输出的声道个数
  273. int out_channel_nb = ffmpeg.av_get_channel_layout_nb_channels((ulong)out_ch_layout);
  274. //存储pcm数据
  275. byte* out_buffer = (byte*)ffmpeg.av_malloc(2 * 44100);
  276. //一帧一帧读取压缩的音频数据AVPacket
  277. while (ffmpeg.av_read_frame(ofmt_ctx, packet) >= 0)
  278. {
  279. // 退出线程
  280. if (exit_thread)
  281. {
  282. break;
  283. }
  284. // 暂停解析
  285. if (pause_thread)
  286. {
  287. while (pause_thread)
  288. {
  289. Thread.Sleep(100);
  290. }
  291. }
  292. if (packet->stream_index == audioindex)
  293. {
  294. //解码AVPacket->AVFrame
  295. ret = ffmpeg.avcodec_decode_audio4(pCodeCtx, frame, &got_frame, packet);
  296. if (ret < 0)
  297. {
  298. Console.WriteLine("音频解码失败");
  299. return -1;
  300. }
  301. // 读取帧数据
  302. if (got_frame>0)
  303. {
  304. frame_count++;
  305. Console.WriteLine("音频帧数:第 "+ frame_count + " 帧");
  306. var data_ = frame->data;
  307. ffmpeg.swr_convert(swrCtx, &out_buffer, 2 * 44100,(byte**)&data_, frame->nb_samples);
  308. //获取sample的size
  309. int out_buffer_size = ffmpeg.av_samples_get_buffer_size(null, out_channel_nb, frame->nb_samples, out_sample_fmt, 1);
  310. //写入文件进行测试
  311. var data=out_buffer;
  312. sdlAudio.PlayAudio((IntPtr)data, out_buffer_size);
  313. }
  314. }
  315. ffmpeg.av_free_packet(packet);
  316. }
  317. }
  318. }
  319. catch (Exception ex)
  320. {
  321. Console.WriteLine(ex);
  322. }
  323. finally
  324. {
  325. if (&ofmt_ctx != null)
  326. {
  327. ffmpeg.avformat_close_input(&ofmt_ctx);//关闭流文件
  328. }
  329. }
  330. IsRun = false;
  331. return 0;
  332. }
  333. /// <summary>
  334. /// 开启线程
  335. /// </summary>
  336. /// <param name="fileName"></param>
  337. /// <param name="sdlVideo"></param>
  338. public void Start(string fileName, SDLHelper sdlVideo,SDLAudio sdlAudio)
  339. {
  340. // 视频线程
  341. threadVideo = new Thread(() =>
  342. {
  343. try
  344. {
  345. RunVideo(fileName, sdlVideo);
  346. }
  347. catch (Exception ex)
  348. {
  349. SQ.Base.ErrorLog.WriteLog4Ex("JT1078CodecForMp4.Run Video", ex);
  350. }
  351. });
  352. threadVideo.IsBackground = true;
  353. threadVideo.Start();
  354. // 音频线程
  355. threadAudio = new Thread(() =>
  356. {
  357. try
  358. {
  359. RunAudio(fileName, sdlAudio);
  360. }
  361. catch (Exception ex)
  362. {
  363. SQ.Base.ErrorLog.WriteLog4Ex("JT1078CodecForMp4.Run Audio", ex);
  364. }
  365. });
  366. threadAudio.IsBackground = true;
  367. threadAudio.Start();
  368. }
  369. /// <summary>
  370. /// 暂停继续
  371. /// </summary>
  372. public void GoOn()
  373. {
  374. pause_thread = false;
  375. }
  376. /// <summary>
  377. /// 暂停
  378. /// </summary>
  379. public void Pause()
  380. {
  381. pause_thread = true;
  382. }
  383. /// <summary>
  384. /// 停止
  385. /// </summary>
  386. public void Stop()
  387. {
  388. exit_thread = true;
  389. }
  390. }
  391. }

暂停、继续、停止在此处的意义不大,因为解析的速度很快。

测试代码及效果图

  1. /// <summary>
  2. /// 播放
  3. /// </summary>
  4. /// <param name="sender"></param>
  5. /// <param name="e"></param>
  6. private void btnPlay_Click(object sender, EventArgs e)
  7. {
  8. // 音视频媒体文件路径
  9. string fileName = "test.mp4";// 表示${Project_home}/bin/Debug/test.mp4
  10. // 线程读取音视频流
  11. jt1078CodecForMp4 = new JT1078CodecForMp4();
  12. jt1078CodecForMp4.Start(fileName,sdlVideo,sdlAudio);
  13. }


注意:此处出现绿色,是不正常的。修改播放方法的数据设置方式:

  1. /// <summary>
  2. /// 播放视频
  3. /// </summary>
  4. /// <param name="width"></param>
  5. /// <param name="height"></param>
  6. /// <param name="pixels"></param>
  7. /// <param name="pixelsSize"></param>
  8. /// <param name="pitch"></param>
  9. /// <returns></returns>
  10. public int SDL_Display(int width, int height, IntPtr pixels, int pixelsSize,
  11. int pitch)
  12. {
  13. lock (this)
  14. {
  15. while (isPause)
  16. {
  17. SDL.SDL_Delay(20);//延迟播放
  18. }
  19. #region SDL 视频数据渲染播放
  20. //设置纹理的数据
  21. sdlrect.x = 0;
  22. sdlrect.y = 0;
  23. sdlrect.w = width;
  24. sdlrect.h = height;
  25. SDL.SDL_UpdateTexture(sdltexture, ref sdlrect, pixels, pitch);
  26. //SDL.SDL_UpdateTexture(sdltexture, IntPtr.Zero, pixels, pitch);//此处代码导致播放窗口绿色阴影
  27. //复制纹理信息到渲染器目标
  28. SDL.SDL_RenderClear(sdltexture);
  29. //SDL.SDL_Rect srcRect = sdlrect;
  30. //SDL.SDL_RenderCopy(sdlrenderer, sdltexture, ref srcRect, ref sdlrect);
  31. SDL.SDL_RenderCopy(sdlrenderer, sdltexture, IntPtr.Zero, IntPtr.Zero);
  32. //视频渲染显示
  33. SDL.SDL_RenderPresent(sdlrenderer);
  34. //SDL.SDL_Delay(40);
  35. //SDL.SDL_PollEvent(out sdlevent);
  36. //switch (sdlevent.type)
  37. //{
  38. // case SDL.SDL_EventType.SDL_QUIT:
  39. // SDL.SDL_Quit();
  40. // return -1;
  41. // default:
  42. // break;
  43. //}
  44. return 0;
  45. }
  46. //SDL.SDL_RenderClear(sdlrenderer);
  47. //SDL.SDL_RenderCopy(sdlrenderer, sdltexture, ref srcRect, ref sdlrect);
  48. //SDL.SDL_RenderPresent(sdlrenderer);
  49. Delay 40ms
  50. //SDL.SDL_Delay(40);
  51. #endregion
  52. //#region SDL 视频数据渲染播放
  53. //设置纹理的数据
  54. sdlrect.x = 0;
  55. sdlrect.y = 0;
  56. sdlrect.w = width;
  57. sdlrect.h = height;
  58. SDL.SDL_UpdateTexture(sdltexture, ref sdlrect, pixels, pitch);
  59. //复制纹理信息到渲染器目标
  60. SDL.SDL_Rect srcRect = sdlrect;
  61. SDL.SDL_RenderCopy(sdlrenderer, sdltexture, ref srcRect, ref sdlrect);
  62. //视频渲染显示
  63. SDL.SDL_RenderPresent(sdlrenderer);
  64. //SDL.SDL_Delay(40);
  65. SDL.SDL_PollEvent(out sdlevent);
  66. switch (sdlevent.type)
  67. {
  68. case SDL.SDL_EventType.SDL_QUIT:
  69. SDL.SDL_Quit();
  70. return -1;
  71. default:
  72. break;
  73. }
  74. return 0;
  75. //#endregion
  76. }
  77. }

关键代码:

  1. SDL.SDL_UpdateTexture(sdltexture, ref sdlrect, pixels, pitch);
  2. //SDL.SDL_UpdateTexture(sdltexture, IntPtr.Zero, pixels, pitch);//此处代码导致播放窗口绿色阴影

修改后效果:


代码改进,采用同一个线程播放音视频:

  1. /// <summary>
  2. /// MP4播放(音视频使用同一个线程)
  3. /// </summary>
  4. public unsafe class JT1078CodecToPlayMp4Two
  5. {
  6. /// <summary>
  7. /// 指示当前解码是否在运行
  8. /// </summary>
  9. public bool IsRun { get; protected set; }
  10. /// <summary>
  11. /// 当前线程
  12. /// </summary>
  13. private Thread thread;
  14. /// <summary>
  15. /// 退出控制
  16. /// </summary>
  17. private bool exit_thread = false;
  18. /// <summary>
  19. /// 暂停控制
  20. /// </summary>
  21. private bool pause_thread = false;
  22. /// <summary>
  23. /// 视频输出流videoindex
  24. /// </summary>
  25. private int videoindex = -1;
  26. /// <summary>
  27. /// 音频输出流audioindex
  28. /// </summary>
  29. private int audioindex = -1;
  30. private bool isInit = false;
  31. int error;
  32. AVFormatContext* ofmt_ctx = null;
  33. AVPacket* packet;
  34. AVCodecContext* pCodecCtx_Video;
  35. AVCodec* pCodec_Video;
  36. AVFrame* pFrame_Video;
  37. AVFrame* pFrameYUV_Video;
  38. SwsContext* sws_ctx_video;
  39. SDLHelper sdlVideo;
  40. SDLAudio sdlAudio;
  41. int out_buffer_size_video;
  42. byte* out_buffer_video;
  43. int video_frame_count, audio_frame_count;
  44. AVCodecContext* pCodeCtx_Audio;
  45. AVCodec* pCodec_Audio;
  46. AVFrame* frame_Audio;
  47. SwrContext* swrCtx_Audio;
  48. byte* out_buffer_audio;
  49. int out_buffer_size_audio;
  50. int out_channel_nb;
  51. AVSampleFormat out_sample_fmt;
  52. /// <summary>
  53. /// 初始化
  54. /// </summary>
  55. /// <param name="fileName"></param>
  56. /// <param name="sdlVideo"></param>
  57. /// <param name="sdlAudio"></param>
  58. /// <returns></returns>
  59. public int Init(string fileName, SDLHelper sdlVideo, SDLAudio sdlAudio)
  60. {
  61. AVFormatContext* ofmt_ctx;
  62. // 注册编解码器
  63. ffmpeg.avcodec_register_all();
  64. // 获取文件信息上下文初始化
  65. ofmt_ctx = ffmpeg.avformat_alloc_context();
  66. this.ofmt_ctx = ofmt_ctx;
  67. // 打开媒体文件
  68. error = ffmpeg.avformat_open_input(&ofmt_ctx, fileName, null, null);
  69. if (error != 0)
  70. {
  71. throw new ApplicationException(FFmpegBinariesHelper.GetErrorMessage(error));
  72. }
  73. // 获取流的通道
  74. for (int i = 0; i < ofmt_ctx->nb_streams; i++)
  75. {
  76. if (ofmt_ctx->streams[i]->codec->codec_type == AVMediaType.AVMEDIA_TYPE_VIDEO)
  77. {
  78. videoindex = i;
  79. Console.WriteLine("video.............." + videoindex);
  80. }
  81. if (ofmt_ctx->streams[i]->codec->codec_type == AVMediaType.AVMEDIA_TYPE_AUDIO)
  82. {
  83. audioindex = i;
  84. Console.WriteLine("audio.............." + audioindex);
  85. }
  86. }
  87. if (videoindex == -1)
  88. {
  89. Console.WriteLine("Couldn't find a video stream.(没有找到视频流)");
  90. return -1;
  91. }
  92. if (audioindex == -1)
  93. {
  94. Console.WriteLine("Couldn't find a audio stream.(没有找到音频流)");
  95. return -1;
  96. }
  97. #region 初始化视频
  98. // 视频流处理
  99. if (videoindex > -1)
  100. {
  101. //获取视频流中的编解码上下文
  102. pCodecCtx_Video = ofmt_ctx->streams[videoindex]->codec;
  103. //根据编解码上下文中的编码id查找对应的解码
  104. pCodec_Video = ffmpeg.avcodec_find_decoder(pCodecCtx_Video->codec_id);
  105. if (pCodec_Video == null)
  106. {
  107. Console.WriteLine("没有找到编码器");
  108. return -1;
  109. }
  110. //打开编码器
  111. if (ffmpeg.avcodec_open2(pCodecCtx_Video, pCodec_Video, null) < 0)
  112. {
  113. Console.WriteLine("编码器无法打开");
  114. return -1;
  115. }
  116. Console.WriteLine("Find a video stream.channel=" + videoindex);
  117. //输出视频信息
  118. var format = ofmt_ctx->iformat->name->ToString();
  119. var len = (ofmt_ctx->duration) / 1000000;
  120. var width = pCodecCtx_Video->width;
  121. var height = pCodecCtx_Video->height;
  122. Console.WriteLine("video format:" + format);
  123. Console.WriteLine("video length:" + len);
  124. Console.WriteLine("video width&height:width=" + width + " height=" + height);
  125. Console.WriteLine("video codec name:" + pCodec_Video->name->ToString());
  126. //准备读取
  127. //AVPacket用于存储一帧一帧的压缩数据(H264)
  128. //AVFrame用于存储解码后的像素数据(YUV)
  129. //内存分配
  130. pFrame_Video = ffmpeg.av_frame_alloc();
  131. //YUV420
  132. pFrameYUV_Video = ffmpeg.av_frame_alloc();
  133. //只有指定了AVFrame的像素格式、画面大小才能真正分配内存
  134. //缓冲区分配内存
  135. out_buffer_size_video = ffmpeg.avpicture_get_size(AVPixelFormat.AV_PIX_FMT_YUV420P, pCodecCtx_Video->width, pCodecCtx_Video->height);
  136. out_buffer_video = (byte*)ffmpeg.av_malloc((ulong)out_buffer_size_video);
  137. //初始化缓冲区
  138. ffmpeg.avpicture_fill((AVPicture*)pFrameYUV_Video, out_buffer_video, AVPixelFormat.AV_PIX_FMT_YUV420P, pCodecCtx_Video->width, pCodecCtx_Video->height);
  139. //用于转码(缩放)的参数,转之前的宽高,转之后的宽高,格式等
  140. sws_ctx_video = ffmpeg.sws_getContext(pCodecCtx_Video->width, pCodecCtx_Video->height, AVPixelFormat.AV_PIX_FMT_YUV420P /*pCodecCtx->pix_fmt*/, pCodecCtx_Video->width, pCodecCtx_Video->height, AVPixelFormat.AV_PIX_FMT_YUV420P, ffmpeg.SWS_BICUBIC, null, null, null);
  141. }
  142. #endregion
  143. #region 初始化音频
  144. // 音频流处理
  145. if (audioindex > -1)
  146. {
  147. //根据索引拿到对应的流,根据流拿到解码器上下文
  148. pCodeCtx_Audio = ofmt_ctx->streams[audioindex]->codec;
  149. //再根据上下文拿到编解码id,通过该id拿到解码器
  150. pCodec_Audio = ffmpeg.avcodec_find_decoder(pCodeCtx_Audio->codec_id);
  151. if (pCodec_Audio == null)
  152. {
  153. Console.WriteLine("没有找到编码器");
  154. return -1;
  155. }
  156. //打开编码器
  157. if (ffmpeg.avcodec_open2(pCodeCtx_Audio, pCodec_Audio, null) < 0)
  158. {
  159. Console.WriteLine("编码器无法打开");
  160. return -1;
  161. }
  162. Console.WriteLine("Find a audio stream. channel=" + audioindex);
  163. //解压缩数据
  164. frame_Audio = ffmpeg.av_frame_alloc();
  165. //frame->16bit 44100 PCM 统一音频采样格式与采样率
  166. swrCtx_Audio = ffmpeg.swr_alloc();
  167. //重采样设置选项-----------------------------------------------------------start
  168. //输入的采样格式
  169. AVSampleFormat in_sample_fmt = pCodeCtx_Audio->sample_fmt;
  170. //输出的采样格式 16bit PCM
  171. out_sample_fmt = AVSampleFormat.AV_SAMPLE_FMT_S16;
  172. //输入的采样率
  173. int in_sample_rate = pCodeCtx_Audio->sample_rate;
  174. //输出的采样率
  175. int out_sample_rate = 44100;
  176. //输入的声道布局
  177. long in_ch_layout = (long)pCodeCtx_Audio->channel_layout;
  178. //输出的声道布局
  179. int out_ch_layout = ffmpeg.AV_CH_LAYOUT_MONO;
  180. ffmpeg.swr_alloc_set_opts(swrCtx_Audio, out_ch_layout, out_sample_fmt, out_sample_rate, in_ch_layout, in_sample_fmt, in_sample_rate, 0, null);
  181. ffmpeg.swr_init(swrCtx_Audio);
  182. //重采样设置选项-----------------------------------------------------------end
  183. //获取输出的声道个数
  184. out_channel_nb = ffmpeg.av_get_channel_layout_nb_channels((ulong)out_ch_layout);
  185. //存储pcm数据
  186. out_buffer_audio = (byte*)ffmpeg.av_malloc(2 * 44100);
  187. }
  188. #endregion
  189. //缓冲区,开辟空间
  190. packet = (AVPacket*)ffmpeg.av_malloc((ulong)sizeof(AVPacket));
  191. // 设置SDL播放对象
  192. this.sdlVideo = sdlVideo;
  193. this.sdlAudio = sdlAudio;
  194. isInit = true;
  195. return 0;
  196. }
  197. /// <summary>
  198. /// 读取音视频流文件并进行播放
  199. /// </summary>
  200. public unsafe int ReadAndPlay()
  201. {
  202. IsRun = true;
  203. exit_thread = false;
  204. pause_thread = false;
  205. thread = Thread.CurrentThread;
  206. //int error, frame_count = 0;
  207. int got_frame, ret;
  208. //SwsContext* pSwsCtx = null;
  209. byte* out_audio_buffer = out_buffer_audio;
  210. try
  211. {
  212. while (ffmpeg.av_read_frame(ofmt_ctx, packet) >= 0)
  213. {
  214. // 退出线程
  215. if (exit_thread)
  216. {
  217. break;
  218. }
  219. // 暂停解析
  220. while (pause_thread)
  221. {
  222. Thread.Sleep(100);
  223. }
  224. #region 视频H264转YUV并使用SDL进行播放
  225. if (packet->stream_index == videoindex)
  226. {
  227. //解码一帧视频压缩数据,得到视频像素数据
  228. ret = ffmpeg.avcodec_decode_video2(pCodecCtx_Video, pFrame_Video, &got_frame, packet);
  229. if (ret < 0)
  230. {
  231. Console.WriteLine("视频解码错误");
  232. return -1;
  233. }
  234. // 读取解码后的帧数据
  235. if (got_frame > 0)
  236. {
  237. double pts = 0; //ffmpeg.av_frame_get_best_effort_timestamp(pFrameYUV_Video);
  238. //VideoState* vs = null;
  239. //vs->video_clock = pts;
  240. //vs->video_st = ofmt_ctx->streams[videoindex];
  241. //pts = synchronize_video(vs, pFrame_Video, pts);
  242. //if (queue_picture(is, pFrame, pts) < 0)
  243. //{
  244. // break;
  245. //}
  246. video_frame_count++;
  247. //存在问题的PTS计算
  248. //int pts = video_frame_count++ * (pCodecCtx_Video->pkt_timebase.num * 1000 / 25 /* pCodecCtx->pkt_timebase.den*/);
  249. Console.WriteLine("视频帧数:第 " + video_frame_count + " 帧");
  250. //AVFrame转为像素格式YUV420,宽高
  251. ffmpeg.sws_scale(sws_ctx_video, pFrame_Video->data, pFrame_Video->linesize, 0, pCodecCtx_Video->height, pFrameYUV_Video->data, pFrameYUV_Video->linesize);
  252. Console.WriteLine("视频: pts= " + packet->pts + " dts=" + packet->dts);
  253. // SDL播放YUV数据:下面两种方式都可以进行播放
  254. sdlVideo.SDL_Display(pCodecCtx_Video->width, pCodecCtx_Video->height, (IntPtr)out_buffer_video, out_buffer_size_video, pFrameYUV_Video->linesize[0]);
  255. //sdlVideo.SDL_Display(pCodecCtx_Video->width, pCodecCtx_Video->height, (IntPtr)pFrameYUV_Video->data[0], out_buffer_size_video, pFrameYUV_Video->linesize[0]);
  256. //DeleyToPlay_Video(packet->pts);
  257. }
  258. }
  259. #endregion
  260. #region 音频AAC转PCM并使用SDL进行播放
  261. if (packet->stream_index == audioindex)
  262. {
  263. //解码AVPacket->AVFrame
  264. ret = ffmpeg.avcodec_decode_audio4(pCodeCtx_Audio, frame_Audio, &got_frame, packet);
  265. if (ret < 0)
  266. {
  267. Console.WriteLine("音频解码失败");
  268. return -1;
  269. }
  270. // 读取帧数据
  271. if (got_frame > 0)
  272. {
  273. audio_frame_count++;
  274. Console.WriteLine("音频帧数:第 " + audio_frame_count + " 帧");
  275. // 变换音频
  276. ffmpeg.swr_convert(swrCtx_Audio, &out_audio_buffer, 2 * 44100, (byte**)&frame_Audio->data, frame_Audio->nb_samples);
  277. // 获取sample的size
  278. out_buffer_size_audio = ffmpeg.av_samples_get_buffer_size(null, out_channel_nb, frame_Audio->nb_samples, out_sample_fmt, 1);
  279. Console.WriteLine("音频: pts= " + packet->pts + " dts=" + packet->dts);
  280. // SDL进行音频播放
  281. sdlAudio.PlayAudio((IntPtr)out_audio_buffer, out_buffer_size_audio);
  282. //DeleyToPlay_Audio(packet->pts);
  283. }
  284. }
  285. #endregion
  286. Thread.Sleep(20);
  287. //释放资源
  288. ffmpeg.av_free_packet(packet);
  289. }
  290. }
  291. catch (Exception ex)
  292. {
  293. Console.WriteLine(ex);
  294. }
  295. finally
  296. {
  297. //if (&ofmt_ctx != null)
  298. //{
  299. // ffmpeg.avformat_close_input(&ofmt_ctx);//关闭流文件
  300. //}
  301. }
  302. IsRun = false;
  303. return 0;
  304. }
  305. /// <summary>
  306. /// 开启线程
  307. /// </summary>
  308. /// <param name="fileName"></param>
  309. /// <param name="sdlVideo"></param>
  310. /// <param name="sdlAudio"></param>
  311. public void Start()
  312. {
  313. if (!isInit)
  314. {
  315. MessageBox.Show("没有初始化");
  316. }
  317. thread = new Thread(() =>
  318. {
  319. try
  320. {
  321. ReadAndPlay();
  322. }
  323. catch (Exception ex)
  324. {
  325. SQ.Base.ErrorLog.WriteLog4Ex("JT1078CodecForMp4.Run Video", ex);
  326. }
  327. });
  328. thread.IsBackground = true;
  329. thread.Start();
  330. }
  331. /// <summary>
  332. /// 暂停继续
  333. /// </summary>
  334. public void GoOnPlay()
  335. {
  336. pause_thread = false;
  337. sdlVideo.PlayVideo();
  338. sdlAudio.PlayAudio();
  339. }
  340. /// <summary>
  341. /// 暂停
  342. /// </summary>
  343. public void Pause()
  344. {
  345. pause_thread = true;
  346. sdlVideo.PauseVideo();
  347. sdlAudio.PauseAudio();
  348. }
  349. /// <summary>
  350. /// 停止
  351. /// </summary>
  352. public void Stop()
  353. {
  354. exit_thread = true;
  355. }
  356. long lastPts_Video = 0;
  357. DateTime lastTS_Video;
  358. long lastPts_Audio = 0;
  359. DateTime lastTS_Audio;
  360. private void DeleyToPlay_Video(long pts)
  361. {
  362. if (lastPts_Video > 0 && lastTS_Video != null)
  363. {
  364. double delay = (DateTime.Now - lastTS_Video).TotalMilliseconds;
  365. var i = (int)(pts - lastPts_Video - delay);
  366. if (i >= 1)
  367. {
  368. Thread.Sleep(i);
  369. }
  370. }
  371. lastTS_Video = DateTime.Now;
  372. lastPts_Video = pts;
  373. }
  374. private void DeleyToPlay_Audio(long pts)
  375. {
  376. if (lastPts_Audio > 0 && lastTS_Audio != null)
  377. {
  378. double delay = (DateTime.Now - lastTS_Audio).TotalMilliseconds;
  379. var i = (int)(pts - lastPts_Audio - delay);
  380. if (i >= 1)
  381. {
  382. Thread.Sleep(i);
  383. }
  384. }
  385. lastTS_Audio = DateTime.Now;
  386. lastPts_Audio = pts;
  387. }
  388. # http://dranger.com/ffmpeg/tutorial05.html
  389. //public struct VideoState
  390. //{
  391. // public double video_clock; // pts of last decoded frame / predicted pts of next decoded frame
  392. // public AVStream* video_st;// video stream
  393. //}
  394. //public unsafe double synchronize_video(VideoState* vs, AVFrame* src_frame, double pts)
  395. //{
  396. // double frame_delay;
  397. // if (pts != 0)
  398. // {
  399. // /* if we have pts, set video clock to it */
  400. // vs->video_clock = pts;
  401. // }
  402. // else
  403. // {
  404. // /* if we aren't given a pts, set it to the clock */
  405. // pts = vs->video_clock;
  406. // }
  407. // /* update the video clock */
  408. // frame_delay = av_q2d(vs->video_st->codec->time_base);
  409. // /* if we are repeating a frame, adjust clock accordingly */
  410. // frame_delay += src_frame->repeat_pict * (frame_delay * 0.5);
  411. // vs->video_clock += frame_delay;
  412. // return pts;
  413. //}
  414. //struct VideoPicture
  415. //{
  416. // double pts;
  417. //}
  418. //int queue_picture(VideoState* vs, AVFrame* pFrame, double pts)
  419. //{
  420. // if (vp->bmp)
  421. // {
  422. // ... convert picture ...
  423. // vp->pts = pts;
  424. // ... alert queue ...
  425. // }
  426. //}
  427. }
解决音视频同步问题版本
  1. using CV.Media.Utils.Filter;
  2. using CV.Video.Base;
  3. using CV.Video.Base.FFmpeg;
  4. using FFmpeg.AutoGen;
  5. using JX;
  6. using System;
  7. using System.Collections.Generic;
  8. using System.Linq;
  9. using System.Runtime.InteropServices;
  10. using System.Text;
  11. using System.Threading;
  12. using System.Threading.Tasks;
  13. using System.Windows.Forms;
  14. using static CvNetVideo.UCVideo;
  15. namespace CvNetVideo.Codec.Video
  16. {
  17. /// <summary>
  18. /// MP4播放(音视频使用同一个线程)
  19. /// </summary>
  20. public unsafe class JT1078CodecToPlayMp4
  21. {
  22. /// <summary>
  23. /// 指示当前解码是否在运行
  24. /// </summary>
  25. public bool IsRun { get; protected set; }
  26. /// <summary>
  27. /// 指示当前解码是否在暂停
  28. /// </summary>
  29. public bool IsPause { get; protected set; }
  30. /// <summary>
  31. /// 当前线程
  32. /// </summary>
  33. public Thread thread;
  34. /// <summary>
  35. /// 退出控制
  36. /// </summary>
  37. private bool exit_thread = false;
  38. /// <summary>
  39. /// 暂停控制
  40. /// </summary>
  41. private bool pause_thread = false;
  42. /// <summary>
  43. /// 视频输出流videoindex
  44. /// </summary>
  45. private int videoindex = -1;
  46. /// <summary>
  47. /// 音频输出流audioindex
  48. /// </summary>
  49. private int audioindex = -1;
  50. /// <summary>
  51. /// 是否初始化
  52. /// </summary>
  53. private bool isInit = false;
  54. int error;
  55. AVFormatContext* ofmt_ctx = null;
  56. AVPacket* packet;
  57. AVCodecContext* pCodecCtx_Video;
  58. AVCodec* pCodec_Video;
  59. AVFrame* pFrame_Video;
  60. AVFrame* pFrameYUV_Video;
  61. SwsContext* sws_ctx_video;
  62. SDLHelper sdlVideo;
  63. SDLAudio sdlAudio;
  64. int out_buffer_size_video;
  65. byte* out_buffer_video;
  66. int video_frame_count, audio_frame_count;
  67. AVCodecContext* pCodeCtx_Audio;
  68. AVCodec* pCodec_Audio;
  69. AVFrame* frame_Audio;
  70. SwrContext* swrCtx_Audio;
  71. byte* out_buffer_audio;
  72. int out_buffer_size_audio;
  73. int out_channel_nb;
  74. AVSampleFormat out_sample_fmt;
  75. int contrast;// 对比度
  76. int brightness;// 亮度
  77. int contrast_last;// 对比度
  78. int brightness_last;// 亮度
  79. //对比度亮度
  80. private VideoFiltering m_video_filtering = new VideoFiltering();
  81. /// <summary>
  82. /// 设置图像对比度和亮度
  83. /// </summary>
  84. /// <param name="contrast"></param>
  85. /// <param name="brightness"></param>
  86. /// <returns></returns>
  87. public void SetContrastAndBrightness(int contrast, int brightness)
  88. {
  89. this.contrast = contrast;
  90. this.brightness = brightness;
  91. }
  92. /// <summary>
  93. /// YUV宽度
  94. /// </summary>
  95. public int YuvWidth { get; set; }
  96. /// <summary>
  97. /// YUV高度
  98. /// </summary>
  99. public int YuvHeight { get; set; }
  100. /// <summary>
  101. /// 记录上一帧数据
  102. /// </summary>
  103. List<AVVideo> list = new List<AVVideo>();
  104. /// <summary>
  105. /// 初始化
  106. /// </summary>
  107. /// <param name="fileName"></param>
  108. /// <param name="sdlVideo"></param>
  109. /// <param name="sdlAudio"></param>
  110. /// <returns></returns>
  111. public int Init(string fileName, SDLHelper sdlVideo, SDLAudio sdlAudio)
  112. {
  113. AVFormatContext* ofmt_ctx;
  114. // 注册编解码器
  115. ffmpeg.avcodec_register_all();
  116. // 获取文件信息上下文初始化
  117. ofmt_ctx = ffmpeg.avformat_alloc_context();
  118. this.ofmt_ctx = ofmt_ctx;
  119. // 打开媒体文件
  120. error = ffmpeg.avformat_open_input(&ofmt_ctx, fileName, null, null);
  121. if (error != 0)
  122. {
  123. throw new ApplicationException(FFmpegBinariesHelper.GetErrorMessage(error));
  124. }
  125. // 获取流的通道
  126. for (int i = 0; i < ofmt_ctx->nb_streams; i++)
  127. {
  128. if (ofmt_ctx->streams[i]->codec->codec_type == AVMediaType.AVMEDIA_TYPE_VIDEO)
  129. {
  130. videoindex = i;
  131. Console.WriteLine("video.............." + videoindex);
  132. }
  133. if (ofmt_ctx->streams[i]->codec->codec_type == AVMediaType.AVMEDIA_TYPE_AUDIO)
  134. {
  135. audioindex = i;
  136. Console.WriteLine("audio.............." + audioindex);
  137. }
  138. }
  139. if (videoindex == -1)
  140. {
  141. Console.WriteLine("Couldn't find a video stream.(没有找到视频流)");
  142. return -1;
  143. }
  144. if (audioindex == -1)
  145. {
  146. Console.WriteLine("Couldn't find a audio stream.(没有找到音频流)");
  147. return -1;
  148. }
  149. #region 初始化视频
  150. // 视频流处理
  151. if (videoindex > -1)
  152. {
  153. //获取视频流中的编解码上下文
  154. pCodecCtx_Video = ofmt_ctx->streams[videoindex]->codec;
  155. //根据编解码上下文中的编码id查找对应的解码
  156. pCodec_Video = ffmpeg.avcodec_find_decoder(pCodecCtx_Video->codec_id);
  157. if (pCodec_Video == null)
  158. {
  159. Console.WriteLine("没有找到编码器");
  160. return -1;
  161. }
  162. //打开编码器
  163. if (ffmpeg.avcodec_open2(pCodecCtx_Video, pCodec_Video, null) < 0)
  164. {
  165. Console.WriteLine("编码器无法打开");
  166. return -1;
  167. }
  168. Console.WriteLine("Find a video stream.channel=" + videoindex);
  169. //输出视频信息
  170. var format = ofmt_ctx->iformat->name->ToString();
  171. var len = (ofmt_ctx->duration) / 1000000;
  172. var width = pCodecCtx_Video->width;
  173. var height = pCodecCtx_Video->height;
  174. Console.WriteLine("video format:" + format);
  175. Console.WriteLine("video length:" + len);
  176. Console.WriteLine("video width&height:width=" + width + " height=" + height);
  177. Console.WriteLine("video codec name:" + pCodec_Video->name->ToString());
  178. //准备读取
  179. //AVPacket用于存储一帧一帧的压缩数据(H264)
  180. //AVFrame用于存储解码后的像素数据(YUV)
  181. //内存分配
  182. pFrame_Video = ffmpeg.av_frame_alloc();
  183. //YUV420
  184. pFrameYUV_Video = ffmpeg.av_frame_alloc();
  185. //只有指定了AVFrame的像素格式、画面大小才能真正分配内存
  186. //缓冲区分配内存
  187. out_buffer_size_video = ffmpeg.avpicture_get_size(AVPixelFormat.AV_PIX_FMT_YUV420P, pCodecCtx_Video->width, pCodecCtx_Video->height);
  188. out_buffer_video = (byte*)ffmpeg.av_malloc((ulong)out_buffer_size_video);
  189. //初始化缓冲区
  190. ffmpeg.avpicture_fill((AVPicture*)pFrameYUV_Video, out_buffer_video, AVPixelFormat.AV_PIX_FMT_YUV420P, pCodecCtx_Video->width, pCodecCtx_Video->height);
  191. //用于转码(缩放)的参数,转之前的宽高,转之后的宽高,格式等
  192. sws_ctx_video = ffmpeg.sws_getContext(pCodecCtx_Video->width, pCodecCtx_Video->height, AVPixelFormat.AV_PIX_FMT_YUV420P /*pCodecCtx->pix_fmt*/, pCodecCtx_Video->width, pCodecCtx_Video->height, AVPixelFormat.AV_PIX_FMT_YUV420P, ffmpeg.SWS_BICUBIC, null, null, null);
  193. }
  194. #endregion
  195. #region 初始化音频
  196. // 音频流处理
  197. if (audioindex > -1)
  198. {
  199. //根据索引拿到对应的流,根据流拿到解码器上下文
  200. pCodeCtx_Audio = ofmt_ctx->streams[audioindex]->codec;
  201. //再根据上下文拿到编解码id,通过该id拿到解码器
  202. pCodec_Audio = ffmpeg.avcodec_find_decoder(pCodeCtx_Audio->codec_id);
  203. if (pCodec_Audio == null)
  204. {
  205. Console.WriteLine("没有找到编码器");
  206. return -1;
  207. }
  208. //打开编码器
  209. if (ffmpeg.avcodec_open2(pCodeCtx_Audio, pCodec_Audio, null) < 0)
  210. {
  211. Console.WriteLine("编码器无法打开");
  212. return -1;
  213. }
  214. Console.WriteLine("Find a audio stream. channel=" + audioindex);
  215. //解压缩数据
  216. frame_Audio = ffmpeg.av_frame_alloc();
  217. //frame->16bit 8000 PCM 统一音频采样格式与采样率
  218. swrCtx_Audio = ffmpeg.swr_alloc();
  219. //重采样设置选项-----------------------------------------------------------start
  220. //输入的采样格式
  221. AVSampleFormat in_sample_fmt = pCodeCtx_Audio->sample_fmt;
  222. //输出的采样格式 16bit PCM
  223. out_sample_fmt = AVSampleFormat.AV_SAMPLE_FMT_S16;
  224. //输入的采样率
  225. int in_sample_rate = pCodeCtx_Audio->sample_rate;
  226. //输出的采样率
  227. int out_sample_rate = 8000;
  228. //输入的声道布局
  229. long in_ch_layout = (long)pCodeCtx_Audio->channel_layout;
  230. //输出的声道布局
  231. int out_ch_layout = ffmpeg.AV_CH_LAYOUT_MONO;
  232. ffmpeg.swr_alloc_set_opts(swrCtx_Audio, out_ch_layout, out_sample_fmt, out_sample_rate, in_ch_layout, in_sample_fmt, in_sample_rate, 0, null);
  233. ffmpeg.swr_init(swrCtx_Audio);
  234. //重采样设置选项-----------------------------------------------------------end
  235. //获取输出的声道个数
  236. out_channel_nb = ffmpeg.av_get_channel_layout_nb_channels((ulong)out_ch_layout);
  237. //存储pcm数据
  238. out_buffer_audio = (byte*)ffmpeg.av_malloc(2 * 8000);
  239. }
  240. #endregion
  241. //缓冲区,开辟空间
  242. packet = (AVPacket*)ffmpeg.av_malloc((ulong)sizeof(AVPacket));
  243. // 设置SDL播放对象
  244. this.sdlVideo = sdlVideo;
  245. this.sdlAudio = sdlAudio;
  246. isInit = true;
  247. return 0;
  248. }
  249. /// <summary>
  250. /// 读取音视频流文件并进行播放
  251. /// </summary>
  252. public unsafe int ReadAndPlay(PlayFinishedDo playFinishedDo)
  253. {
  254. IsRun = true;
  255. exit_thread = false;
  256. pause_thread = false;
  257. thread = Thread.CurrentThread;
  258. //int error, frame_count = 0;
  259. int got_frame, ret;
  260. //SwsContext* pSwsCtx = null;
  261. byte* out_audio_buffer = out_buffer_audio;
  262. try
  263. {
  264. AVStream* video_stream = ofmt_ctx->streams[videoindex];
  265. while (ffmpeg.av_read_frame(ofmt_ctx, packet) >= 0&& !exit_thread)
  266. {
  267. // 暂停解析
  268. while (pause_thread||isLastFrame)
  269. {
  270. // 退出线程
  271. if (exit_thread)
  272. {
  273. break;
  274. }
  275. Thread.Sleep(10);
  276. }
  277. // 退出线程
  278. if (exit_thread)
  279. {
  280. break;
  281. }
  282. // 此处记录视频的第一帧和第一帧的开始时间
  283. if (firstPts == -1 && packet->stream_index == videoindex)
  284. {
  285. firstPts = packet->pts * 1000 / (video_stream->time_base.den / video_stream->time_base.num);
  286. startTS = DateTime.Now;
  287. }
  288. // 针对视频做延时播放,音频自然播放就行不做处理
  289. if (packet->stream_index == videoindex)
  290. {
  291. long pts_1 = packet->pts * 1000 / (video_stream->time_base.den / video_stream->time_base.num);
  292. DeleyToPlay(pts_1);
  293. }
  294. #region 视频H264转YUV并使用SDL进行播放
  295. if (packet->stream_index == videoindex)
  296. {
  297. //解码一帧视频压缩数据,得到视频像素数据
  298. ret = ffmpeg.avcodec_decode_video2(pCodecCtx_Video, pFrame_Video, &got_frame, packet);
  299. if (ret < 0)
  300. {
  301. Console.WriteLine("视频解码错误");
  302. return -1;
  303. }
  304. //滤波,亮度,对比度===参考JT1078ToYuv -----------开始
  305. int width = pCodecCtx_Video->width;
  306. int height = pCodecCtx_Video->height;
  307. if (contrast != contrast_last || brightness != brightness_last)
  308. {
  309. m_video_filtering.Reset(width, height, contrast, brightness);
  310. contrast_last = contrast;
  311. brightness_last = brightness;
  312. }
  313. //滤波,亮度,对比度===参考JT1078ToYuv -----------结束
  314. // 读取解码后的帧数据
  315. if (got_frame > 0)
  316. {
  317. video_frame_count++;
  318. //>>>>滤波,亮度,对比度===参考JT1078ToYuv -----------开始
  319. AVFrame* frame_filter;
  320. ret = m_video_filtering.Filter(pFrame_Video, &frame_filter);
  321. //>>>>滤波,亮度,对比度===参考JT1078ToYuv -----------结束
  322. //AVFrame转为像素格式YUV420,宽高
  323. ffmpeg.sws_scale(sws_ctx_video, frame_filter->data, frame_filter->linesize, 0, pCodecCtx_Video->height, pFrameYUV_Video->data, pFrameYUV_Video->linesize);
  324. // 记录上一帧图像保持10个帧数
  325. AVVideo videoFrame = new AVVideo(pCodecCtx_Video->width, pCodecCtx_Video->height, (IntPtr)out_buffer_video, out_buffer_size_video, pFrameYUV_Video->linesize[0]);
  326. list.Add(videoFrame);
  327. if (list.Count > 10) list.RemoveAt(0);
  328. // SDL播放YUV数据:下面两种方式都可以进行播放
  329. sdlVideo.SDL_Display(pCodecCtx_Video->width, pCodecCtx_Video->height,YuvWidth, YuvHeight, (IntPtr)out_buffer_video, out_buffer_size_video, pFrameYUV_Video->linesize[0]);
  330. //sdlVideo.SDL_Display(pCodecCtx_Video->width, pCodecCtx_Video->height, (IntPtr)pFrameYUV_Video->data[0], out_buffer_size_video, pFrameYUV_Video->linesize[0]);
  331. // 播放下一帧时进行暂停
  332. if (isNextFrame)
  333. {
  334. Pause();
  335. isNextFrame = false;
  336. }
  337. // 释放滤波
  338. m_video_filtering.UnrefFrame();
  339. }
  340. }
  341. #endregion
  342. #region 音频AAC转PCM并使用SDL进行播放
  343. if (packet->stream_index == audioindex)
  344. {
  345. //解码AVPacket->AVFrame
  346. ret = ffmpeg.avcodec_decode_audio4(pCodeCtx_Audio, frame_Audio, &got_frame, packet);
  347. if (ret < 0)
  348. {
  349. Console.WriteLine("音频解码失败");
  350. return -1;
  351. }
  352. // 读取帧数据
  353. if (got_frame > 0)
  354. {
  355. audio_frame_count++;
  356. // 变换音频
  357. ffmpeg.swr_convert(swrCtx_Audio, &out_audio_buffer, 2 * 8000, (byte**)&frame_Audio->data, frame_Audio->nb_samples);
  358. // 获取sample的size
  359. out_buffer_size_audio = ffmpeg.av_samples_get_buffer_size(null, out_channel_nb, frame_Audio->nb_samples, out_sample_fmt, 1);
  360. // SDL进行音频播放
  361. sdlAudio.PlayAudio((IntPtr)out_audio_buffer, out_buffer_size_audio);
  362. }
  363. }
  364. #endregion
  365. //释放资源
  366. ffmpeg.av_free_packet(packet);
  367. Thread.Sleep(10);
  368. }
  369. }
  370. catch (Exception ex)
  371. {
  372. Console.WriteLine(ex);
  373. }
  374. finally
  375. {
  376. // 释放文件流
  377. ffmpeg.avformat_free_context(ofmt_ctx);
  378. // 修改右键菜单回调函数
  379. playFinishedDo.Invoke();
  380. }
  381. IsRun = false;
  382. IsPause = false;
  383. return 0;
  384. }
  385. bool isLastFrame = false;
  386. bool isNextFrame = false;
  387. bool playFastly = false;
  388. bool playSlowly = false;
  389. int play_speed = 1;
  390. long firstPts = -1;
  391. DateTime startTS;
  392. /// <summary>
  393. /// 控制快慢
  394. /// </summary>
  395. /// <param name="pts"></param>
  396. /// <param name="speed"></param>
  397. private void DeleyToPlay(long pts)
  398. {
  399. int delayTime = 0;
  400. try
  401. {
  402. // 计算延时
  403. double delay = (DateTime.Now - startTS).TotalMilliseconds;
  404. var i = (int)(pts - firstPts - delay);
  405. if (i >= 100)
  406. {
  407. delayTime = 40;
  408. delayTime = ControlFastOrSlow(delayTime);
  409. }
  410. else if (i >= 300)
  411. {
  412. delayTime = 60;
  413. delayTime = ControlFastOrSlow(delayTime);
  414. }
  415. else if (i >= 500)
  416. {
  417. delayTime = 100;
  418. delayTime = ControlFastOrSlow(delayTime);
  419. }
  420. }
  421. catch
  422. {
  423. Console.WriteLine("Counting delay time error ");
  424. }
  425. finally
  426. {
  427. Console.WriteLine("Counting delay time = " + delayTime+ " play_speed="+ play_speed);
  428. if (delayTime > 0)
  429. Thread.Sleep(delayTime);
  430. }
  431. }
  432. /// <summary>
  433. /// 控制快慢
  434. /// </summary>
  435. /// <param name="delayTime"></param>
  436. private int ControlFastOrSlow(int delayTime)
  437. {
  438. if (playFastly)
  439. {
  440. // 快放
  441. delayTime /= play_speed;
  442. }
  443. else if (playSlowly)
  444. {
  445. // 慢放
  446. delayTime *= play_speed;
  447. }
  448. return delayTime;
  449. }
  450. /// <summary>
  451. /// 开启线程
  452. /// </summary>
  453. /// <param name="fileName"></param>
  454. /// <param name="sdlVideo"></param>
  455. /// <param name="sdlAudio"></param>
  456. public void Start(PlayFinishedDo playFinishedDo)
  457. {
  458. if (!isInit)
  459. {
  460. MessageBox.Show("没有初始化");
  461. }
  462. thread = new Thread(() =>
  463. {
  464. try
  465. {
  466. ReadAndPlay(playFinishedDo);
  467. }
  468. catch (Exception ex)
  469. {
  470. SQ.Base.ErrorLog.WriteLog4Ex("JT1078CodecForMp4.Run Video", ex);
  471. }
  472. });
  473. thread.IsBackground = true;
  474. thread.Start();
  475. }
  476. /// <summary>
  477. /// 暂停继续
  478. /// </summary>
  479. public void GoOnPlay()
  480. {
  481. // 重置第一帧pts,处理暂停后音视频不同步
  482. firstPts = -1;
  483. // 继续的相关操作和变量修改
  484. pause_thread = false;
  485. IsPause = pause_thread;
  486. sdlVideo.PlayVideo();
  487. sdlAudio.PlayAudio();
  488. }
  489. /// <summary>
  490. /// 暂停
  491. /// </summary>
  492. public void Pause()
  493. {
  494. // 暂停的相关操作和变量修改
  495. pause_thread = true;
  496. IsPause = pause_thread;
  497. sdlVideo.PauseVideo();
  498. sdlAudio.PauseAudio();
  499. }
  500. /// <summary>
  501. /// 停止
  502. /// </summary>
  503. public void Stop()
  504. {
  505. exit_thread = true;
  506. if (thread != null && thread.IsAlive)
  507. {
  508. thread.Abort();
  509. thread.Join();
  510. thread = null;
  511. }
  512. }
  513. /// <summary>
  514. /// 快放
  515. /// </summary>
  516. public void PlayFast()
  517. {
  518. if (pause_thread)
  519. {
  520. // 激活播放
  521. GoOnPlay();
  522. }
  523. if (playSlowly)
  524. {
  525. play_speed = 1;
  526. playSlowly = false;
  527. }
  528. else
  529. {
  530. play_speed++;
  531. }
  532. playFastly = true;
  533. }
  534. /// <summary>
  535. /// 慢放
  536. /// </summary>
  537. public void PlaySlow()
  538. {
  539. if (pause_thread)
  540. {
  541. // 激活播放
  542. GoOnPlay();
  543. }
  544. if (playFastly)
  545. {
  546. play_speed = 1;
  547. playFastly = false;
  548. }
  549. else
  550. {
  551. play_speed++;
  552. }
  553. playSlowly = true;
  554. }
  555. /// <summary>
  556. /// 上一帧
  557. /// </summary>
  558. public void PlayLastFrame()
  559. {
  560. // 修改上一帧标志
  561. isLastFrame = true;
  562. // 每点击一次向前播一帧
  563. if (list.Count>0)
  564. {
  565. Console.WriteLine("剩余播放帧:"+ list.Count);
  566. // 激活播放
  567. GoOnPlay();
  568. AVVideo lastFrame = list.Last();
  569. // 播放上一帧图像
  570. sdlVideo.SDL_Display(lastFrame.width, lastFrame.height, lastFrame.pixels, lastFrame.pixelsSize, lastFrame.pitch);
  571. // 修改上一帧标志
  572. isLastFrame = false;
  573. // 移除已看过的帧
  574. list.Remove(lastFrame);
  575. Thread.Sleep(10);
  576. Pause();
  577. }
  578. }
  579. /// <summary>
  580. /// 下一帧
  581. /// </summary>
  582. public void PlayNextFrame()
  583. {
  584. // 暂停以区分帧
  585. Pause();
  586. // 播放以完成下一帧图像显示或声音播放
  587. GoOnPlay();
  588. // 下一帧播放完成暂停标志
  589. isNextFrame = true;
  590. }
  591. }
  592. class Media
  593. {
  594. /// <summary>
  595. /// 0:video,1:audio
  596. /// </summary>
  597. public int type { get; set; }
  598. /// <summary>
  599. /// pts value
  600. /// </summary>
  601. public long pts { get; set; }
  602. }
  603. class AVVideo : Media
  604. {
  605. public int width { get; set; }
  606. public int height { get; set; }
  607. public IntPtr pixels { get; set; }
  608. public int pixelsSize { get; set; }
  609. public int pitch { get; set; }
  610. public AVVideo(int width, int height, IntPtr pixels, int pixelsSize, int pitch)
  611. {
  612. this.width = width;
  613. this.height = height;
  614. this.pixels = pixels;
  615. this.pixelsSize = pixelsSize;
  616. this.pitch = pitch;
  617. }
  618. }
  619. class AVAudio : Media
  620. {
  621. public IntPtr pcm { get; set; }
  622. public int len { get; set; }
  623. public AVAudio(IntPtr pcm, int len)
  624. {
  625. this.pcm = pcm;
  626. this.len = len;
  627. }
  628. }
  629. }


相关技术文章

点击QQ咨询
开通会员
返回顶部
×
微信扫码支付
微信扫码支付
确定支付下载
请使用微信描二维码支付
×

提示信息

×

选择支付方式

  • 微信支付
  • 支付宝付款
确定支付下载