C# 使用SDL2實(shí)現(xiàn)Mp4文件播放音視頻操作
播放音視頻的關(guān)鍵:視頻的格式是H264,音頻的格式是AAC。使用ffmpeg探測(cè)流的方式來(lái)實(shí)現(xiàn)音視頻流的解碼播放。
數(shù)據(jù)處理邏輯:H264->YUV AAC->PCM。
SDL2工具類
using SDL2;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace CvNetVideo
{
public unsafe class SDLHelper
{
private IntPtr screen;
private IntPtr sdlrenderer;
private IntPtr sdltexture;
SDL.SDL_Rect sdlrect;
SDL.SDL_Event sdlevent;
bool isInit = false;
public SDLHelper()
{
}
public void SDL_MaximizeWindow()
{
}
public int SDL_Init(int width, int height, IntPtr intPtr)
{
lock (this)
{
if (!isInit)
{
// 初始化調(diào)用SDL.SDL_Init(SDL.SDL_INIT_VIDEO | SDL.SDL_INIT_AUDIO | SDL.SDL_INIT_TIMER)
if (SDL.SDL_Init(SDL.SDL_INIT_VIDEO | SDL.SDL_INIT_AUDIO | SDL.SDL_INIT_TIMER) < 0)
{
Console.WriteLine("Could not initialize SDL - {0}\n", SDL.SDL_GetError());
return -1;
}
isInit = true;
}
#region SDL調(diào)用
if (sdltexture != IntPtr.Zero)
{
SDL.SDL_DestroyTexture(sdltexture);
}
if (sdlrenderer != IntPtr.Zero)
{
SDL.SDL_DestroyRenderer(sdlrenderer);
}
if (screen != IntPtr.Zero)
{
SDL.SDL_DestroyWindow(screen);
SDL.SDL_RaiseWindow(screen);
SDL.SDL_RestoreWindow(screen);
}
//創(chuàng)建顯示窗口
screen = SDL.SDL_CreateWindowFrom(intPtr);
SDL.SDL_ShowWindow(screen);
SDL.SDL_SetWindowSize(screen, width, height);
//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);
//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);
if (screen == IntPtr.Zero)
{
Console.WriteLine("Can't creat a window:{0}\n", SDL.SDL_GetError());
return -1;
}
//創(chuàng)建渲染器
sdlrenderer = SDL.SDL_CreateRenderer(screen, -1, SDL.SDL_RendererFlags.SDL_RENDERER_ACCELERATED);
//創(chuàng)建紋理
sdltexture = SDL.SDL_CreateTexture(sdlrenderer, SDL.SDL_PIXELFORMAT_IYUV, (int)SDL.SDL_TextureAccess.SDL_TEXTUREACCESS_STREAMING, width, height);
#endregion
return 0;
}
}
public int SDL_Display(int width, int height, IntPtr pixels, int pixelsSize,
int pitch)
{
lock (this)
{
#region SDL 視頻數(shù)據(jù)渲染播放
//設(shè)置紋理的數(shù)據(jù)
sdlrect.x = 0;
sdlrect.y = 0;
sdlrect.w = width;
sdlrect.h = height;
//SDL.SDL_UpdateTexture(sdltexture, ref sdlrect, pixels, pitch);
SDL.SDL_UpdateTexture(sdltexture, IntPtr.Zero, pixels, pitch);
//復(fù)制紋理信息到渲染器目標(biāo)
SDL.SDL_RenderClear(sdltexture);
//SDL.SDL_Rect srcRect = sdlrect;
//SDL.SDL_RenderCopy(sdlrenderer, sdltexture, ref srcRect, ref sdlrect);
SDL.SDL_RenderCopy(sdlrenderer, sdltexture, IntPtr.Zero, IntPtr.Zero);
//視頻渲染顯示
SDL.SDL_RenderPresent(sdlrenderer);
return 0;
}
#endregion
}
}
public unsafe class SDLAudio
{
class aa
{
public byte[] pcm;
public int len;
}
int lastIndex = 0;
private List<aa> data = new List<aa>();
//private List<byte> data = new List<byte>();
SDL.SDL_AudioCallback Callback;
public void PlayAudio(IntPtr pcm, int len)
{
lock (this)
{
byte[] bts = new byte[len];
Marshal.Copy(pcm, bts, 0, len);
data.Add(new aa
{
len = len,
pcm = bts
});
}
//SDL.SDL_Delay(10);
}
void SDL_AudioCallback(IntPtr userdata, IntPtr stream, int len)
{
SDL 2.0
SDL.SDL_RWFromMem(stream, 0, len);
//if (audio_len == 0)
// return;
//len = (len > audio_len ? audio_len : len);
if (data.Count == 0)
{
for (int i = 0; i < len; i++)
{
((byte*)stream)[i] = 0;
}
return;
}
for (int i = 0; i < len; i++)
{
if (data[0].len > i)
{
((byte*)stream)[i] = data[0].pcm[i];
}
else
((byte*)stream)[i] = 0;
}
data.RemoveAt(0);
}
public int SDL_Init()
{
Callback = SDL_AudioCallback;
#region SDL調(diào)用
初始化調(diào)用SDL.SDL_Init(SDL.SDL_INIT_VIDEO | SDL.SDL_INIT_AUDIO | SDL.SDL_INIT_TIMER)
//if (SDL.SDL_Init(SDL.SDL_INIT_VIDEO | SDL.SDL_INIT_AUDIO | SDL.SDL_INIT_TIMER) < 0)
//{
// Console.WriteLine("Could not initialize SDL - {0}\n", SDL.SDL_GetError());
// return -1;
//}
#endregion
SDL.SDL_AudioSpec wanted_spec = new SDL.SDL_AudioSpec();
wanted_spec.freq = 8000;
wanted_spec.format = SDL.AUDIO_S16;
wanted_spec.channels = 1;
wanted_spec.silence = 0;
wanted_spec.samples = 320;
wanted_spec.callback = Callback;
if (SDL.SDL_OpenAudio(ref wanted_spec, IntPtr.Zero) < 0)
{
Console.WriteLine("can't open audio.");
return -1;
}
//Play
SDL.SDL_PauseAudio(0);
return 0;
}
}
}
SDL實(shí)現(xiàn)了基礎(chǔ)的播放功能。
C# Mp4文件音視頻編碼器類
using CV.Video.Base;
using CV.Video.Base.FFmpeg;
using FFmpeg.AutoGen;
using JX;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace CvNetVideo.Codec.Video
{
public unsafe class JT1078CodecForMp4
{
/// <summary>
/// 指示當(dāng)前解碼是否在運(yùn)行
/// </summary>
public bool IsRun { get; protected set; }
/// <summary>
/// 視頻線程
/// </summary>
private Thread threadVideo;
/// <summary>
/// 音頻線程
/// </summary>
private Thread threadAudio;
/// <summary>
/// 退出控制
/// </summary>
private bool exit_thread = false;
/// <summary>
/// 暫??刂?
/// </summary>
private bool pause_thread = false;
/// <summary>
/// 視頻輸出流videoindex
/// </summary>
private int videoindex = -1;
/// <summary>
/// 音頻輸出流audioindex
/// </summary>
private int audioindex = -1;
/// <summary>
/// 視頻H264轉(zhuǎn)YUV并使用SDL進(jìn)行播放
/// </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
{
// 注冊(cè)編解碼器
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(FFmpegBinariesHelper.GetErrorMessage(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.(沒(méi)有找到視頻流)");
return -1;
}
// 視頻流處理
if (videoindex > -1)
{
//獲取視頻流中的編解碼上下文
AVCodecContext* pCodecCtx = ofmt_ctx->streams[videoindex]->codec;
//根據(jù)編解碼上下文中的編碼id查找對(duì)應(yīng)的解碼
AVCodec* pCodec = ffmpeg.avcodec_find_decoder(pCodecCtx->codec_id);
if (pCodec == null)
{
Console.WriteLine("沒(méi)有找到編碼器");
return -1;
}
//打開編碼器
if (ffmpeg.avcodec_open2(pCodecCtx, pCodec, null) < 0)
{
Console.WriteLine("編碼器無(wú)法打開");
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());
//準(zhǔn)備讀取
//AVPacket用于存儲(chǔ)一幀一幀的壓縮數(shù)據(jù)(H264)
//緩沖區(qū),開辟空間
AVPacket* packet = (AVPacket*)ffmpeg.av_malloc((ulong)sizeof(AVPacket));
//AVFrame用于存儲(chǔ)解碼后的像素?cái)?shù)據(jù)(YUV)
//內(nèi)存分配
AVFrame* pFrame = ffmpeg.av_frame_alloc();
//YUV420
AVFrame* pFrameYUV = ffmpeg.av_frame_alloc();
//只有指定了AVFrame的像素格式、畫面大小才能真正分配內(nèi)存
//緩沖區(qū)分配內(nèi)存
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);
//初始化緩沖區(qū)
ffmpeg.avpicture_fill((AVPicture*)pFrameYUV, out_buffer, AVPixelFormat.AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height);
//用于轉(zhuǎn)碼(縮放)的參數(shù),轉(zhuǎn)之前的寬高,轉(zhuǎn)之后的寬高,格式等
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);
while (ffmpeg.av_read_frame(ofmt_ctx, packet) >= 0)
{
// 退出線程
if (exit_thread)
{
break;
}
// 暫停解析
if (pause_thread)
{
while (pause_thread)
{
Thread.Sleep(100);
}
}
//只要視頻壓縮數(shù)據(jù)(根據(jù)流的索引位置判斷)
if (packet->stream_index == videoindex)
{
//解碼一幀視頻壓縮數(shù)據(jù),得到視頻像素?cái)?shù)據(jù)
ret = ffmpeg.avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);
if (ret < 0)
{
Console.WriteLine("視頻解碼錯(cuò)誤");
return -1;
}
// 讀取解碼后的幀數(shù)據(jù)
if (got_picture>0)
{
frame_count++;
Console.WriteLine("視頻幀數(shù):第 " + frame_count + " 幀");
//AVFrame轉(zhuǎn)為像素格式Y(jié)UV420,寬高
ffmpeg.sws_scale(sws_ctx, pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize);
//SDL播放YUV數(shù)據(jù)
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);//關(guān)閉流文件
}
}
IsRun = false;
return 0;
}
/// <summary>
/// 音頻AAC轉(zhuǎn)PCM并使用SDL進(jìn)行播放
/// </summary>
/// <param name="fileName"></param>
/// <param name="sdlAudio"></param>
/// <returns></returns>
public unsafe int RunAudio(string fileName, SDLAudio sdlAudio)
{
IsRun = true;
exit_thread = false;
pause_thread = false;
threadAudio = Thread.CurrentThread;
int error, frame_count = 0;
int got_frame, ret;
AVFormatContext* ofmt_ctx = null;
SwsContext* pSwsCtx = null;
IntPtr convertedFrameBufferPtr = IntPtr.Zero;
try
{
// 注冊(cè)編解碼器
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(FFmpegBinariesHelper.GetErrorMessage(error));
}
// 獲取流的通道
for (int i = 0; i < ofmt_ctx->nb_streams; i++)
{
if (ofmt_ctx->streams[i]->codec->codec_type == AVMediaType.AVMEDIA_TYPE_AUDIO)
{
audioindex = i;
Console.WriteLine("audio.............." + audioindex);
}
}
if (audioindex == -1)
{
Console.WriteLine("Couldn't find a audio stream.(沒(méi)有找到音頻流)");
return -1;
}
// 音頻流處理
if (audioindex > -1)
{
//根據(jù)索引拿到對(duì)應(yīng)的流,根據(jù)流拿到解碼器上下文
AVCodecContext* pCodeCtx = ofmt_ctx->streams[audioindex]->codec;
//再根據(jù)上下文拿到編解碼id,通過(guò)該id拿到解碼器
AVCodec* pCodec = ffmpeg.avcodec_find_decoder(pCodeCtx->codec_id);
if (pCodec == null)
{
Console.WriteLine("沒(méi)有找到編碼器");
return -1;
}
//打開編碼器
if (ffmpeg.avcodec_open2(pCodeCtx,pCodec, null)<0)
{
Console.WriteLine("編碼器無(wú)法打開");
return -1;
}
Console.WriteLine("Find a audio stream. channel=" + audioindex);
//編碼數(shù)據(jù)
AVPacket* packet = (AVPacket*)ffmpeg.av_malloc((ulong)(sizeof(AVPacket)));
//解壓縮數(shù)據(jù)
AVFrame* frame = ffmpeg.av_frame_alloc();
//frame->16bit 44100 PCM 統(tǒng)一音頻采樣格式與采樣率
SwrContext* swrCtx = ffmpeg.swr_alloc();
//重采樣設(shè)置選項(xiàng)-----------------------------------------------------------start
//輸入的采樣格式
AVSampleFormat in_sample_fmt = pCodeCtx->sample_fmt;
//輸出的采樣格式 16bit PCM
AVSampleFormat out_sample_fmt = AVSampleFormat.AV_SAMPLE_FMT_S16;
//輸入的采樣率
int in_sample_rate = pCodeCtx->sample_rate;
//輸出的采樣率
int out_sample_rate = 44100;
//輸入的聲道布局
long in_ch_layout = (long)pCodeCtx->channel_layout;
//輸出的聲道布局
int out_ch_layout = ffmpeg.AV_CH_LAYOUT_MONO;
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);
ffmpeg.swr_init(swrCtx);
//重采樣設(shè)置選項(xiàng)-----------------------------------------------------------end
//獲取輸出的聲道個(gè)數(shù)
int out_channel_nb = ffmpeg.av_get_channel_layout_nb_channels((ulong)out_ch_layout);
//存儲(chǔ)pcm數(shù)據(jù)
byte* out_buffer = (byte*)ffmpeg.av_malloc(2 * 44100);
//一幀一幀讀取壓縮的音頻數(shù)據(jù)AVPacket
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 == audioindex)
{
//解碼AVPacket->AVFrame
ret = ffmpeg.avcodec_decode_audio4(pCodeCtx, frame, &got_frame, packet);
if (ret < 0)
{
Console.WriteLine("音頻解碼失敗");
return -1;
}
// 讀取幀數(shù)據(jù)
if (got_frame>0)
{
frame_count++;
Console.WriteLine("音頻幀數(shù):第 "+ frame_count + " 幀");
var data_ = frame->data;
ffmpeg.swr_convert(swrCtx, &out_buffer, 2 * 44100,(byte**)&data_, frame->nb_samples);
//獲取sample的size
int out_buffer_size = ffmpeg.av_samples_get_buffer_size(null, out_channel_nb, frame->nb_samples, out_sample_fmt, 1);
//寫入文件進(jìn)行測(cè)試
var data=out_buffer;
sdlAudio.PlayAudio((IntPtr)data, out_buffer_size);
}
}
ffmpeg.av_free_packet(packet);
}
}
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
finally
{
if (&ofmt_ctx != null)
{
ffmpeg.avformat_close_input(&ofmt_ctx);//關(guān)閉流文件
}
}
IsRun = false;
return 0;
}
/// <summary>
/// 開啟線程
/// </summary>
/// <param name="fileName"></param>
/// <param name="sdlVideo"></param>
public void Start(string fileName, SDLHelper sdlVideo,SDLAudio sdlAudio)
{
// 視頻線程
threadVideo = new Thread(() =>
{
try
{
RunVideo(fileName, sdlVideo);
}
catch (Exception ex)
{
SQ.Base.ErrorLog.WriteLog4Ex("JT1078CodecForMp4.Run Video", ex);
}
});
threadVideo.IsBackground = true;
threadVideo.Start();
// 音頻線程
threadAudio = new Thread(() =>
{
try
{
RunAudio(fileName, sdlAudio);
}
catch (Exception ex)
{
SQ.Base.ErrorLog.WriteLog4Ex("JT1078CodecForMp4.Run Audio", ex);
}
});
threadAudio.IsBackground = true;
threadAudio.Start();
}
/// <summary>
/// 暫停繼續(xù)
/// </summary>
public void GoOn()
{
pause_thread = false;
}
/// <summary>
/// 暫停
/// </summary>
public void Pause()
{
pause_thread = true;
}
/// <summary>
/// 停止
/// </summary>
public void Stop()
{
exit_thread = true;
}
}
}
暫停、繼續(xù)、停止在此處的意義不大,因?yàn)榻馕龅乃俣群芸臁?/p>
測(cè)試代碼及效果圖

/// <summary>
/// 播放
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnPlay_Click(object sender, EventArgs e)
{
// 音視頻媒體文件路徑
string fileName = "test.mp4";// 表示${Project_home}/bin/Debug/test.mp4
// 線程讀取音視頻流
jt1078CodecForMp4 = new JT1078CodecForMp4();
jt1078CodecForMp4.Start(fileName,sdlVideo,sdlAudio);
}
注意:此處出現(xiàn)綠色,是不正常的。修改播放方法的數(shù)據(jù)設(shè)置方式:
/// <summary>
/// 播放視頻
/// </summary>
/// <param name="width"></param>
/// <param name="height"></param>
/// <param name="pixels"></param>
/// <param name="pixelsSize"></param>
/// <param name="pitch"></param>
/// <returns></returns>
public int SDL_Display(int width, int height, IntPtr pixels, int pixelsSize,
int pitch)
{
lock (this)
{
while (isPause)
{
SDL.SDL_Delay(20);//延遲播放
}
#region SDL 視頻數(shù)據(jù)渲染播放
//設(shè)置紋理的數(shù)據(jù)
sdlrect.x = 0;
sdlrect.y = 0;
sdlrect.w = width;
sdlrect.h = height;
SDL.SDL_UpdateTexture(sdltexture, ref sdlrect, pixels, pitch);
//SDL.SDL_UpdateTexture(sdltexture, IntPtr.Zero, pixels, pitch);//此處代碼導(dǎo)致播放窗口綠色陰影
//復(fù)制紋理信息到渲染器目標(biāo)
SDL.SDL_RenderClear(sdltexture);
//SDL.SDL_Rect srcRect = sdlrect;
//SDL.SDL_RenderCopy(sdlrenderer, sdltexture, ref srcRect, ref sdlrect);
SDL.SDL_RenderCopy(sdlrenderer, sdltexture, IntPtr.Zero, IntPtr.Zero);
//視頻渲染顯示
SDL.SDL_RenderPresent(sdlrenderer);
//SDL.SDL_Delay(40);
//SDL.SDL_PollEvent(out sdlevent);
//switch (sdlevent.type)
//{
// case SDL.SDL_EventType.SDL_QUIT:
// SDL.SDL_Quit();
// return -1;
// default:
// break;
//}
return 0;
}
//SDL.SDL_RenderClear(sdlrenderer);
//SDL.SDL_RenderCopy(sdlrenderer, sdltexture, ref srcRect, ref sdlrect);
//SDL.SDL_RenderPresent(sdlrenderer);
Delay 40ms
//SDL.SDL_Delay(40);
#endregion
//#region SDL 視頻數(shù)據(jù)渲染播放
//設(shè)置紋理的數(shù)據(jù)
sdlrect.x = 0;
sdlrect.y = 0;
sdlrect.w = width;
sdlrect.h = height;
SDL.SDL_UpdateTexture(sdltexture, ref sdlrect, pixels, pitch);
//復(fù)制紋理信息到渲染器目標(biāo)
SDL.SDL_Rect srcRect = sdlrect;
SDL.SDL_RenderCopy(sdlrenderer, sdltexture, ref srcRect, ref sdlrect);
//視頻渲染顯示
SDL.SDL_RenderPresent(sdlrenderer);
//SDL.SDL_Delay(40);
SDL.SDL_PollEvent(out sdlevent);
switch (sdlevent.type)
{
case SDL.SDL_EventType.SDL_QUIT:
SDL.SDL_Quit();
return -1;
default:
break;
}
return 0;
//#endregion
}
}
關(guān)鍵代碼:
SDL.SDL_UpdateTexture(sdltexture, ref sdlrect, pixels, pitch);
//SDL.SDL_UpdateTexture(sdltexture, IntPtr.Zero, pixels, pitch);//此處代碼導(dǎo)致播放窗口綠色陰影
修改后效果:

代碼改進(jìn),采用同一個(gè)線程播放音視頻:
/// <summary>
/// MP4播放(音視頻使用同一個(gè)線程)
/// </summary>
public unsafe class JT1078CodecToPlayMp4Two
{
/// <summary>
/// 指示當(dāng)前解碼是否在運(yùn)行
/// </summary>
public bool IsRun { get; protected set; }
/// <summary>
/// 當(dāng)前線程
/// </summary>
private Thread thread;
/// <summary>
/// 退出控制
/// </summary>
private bool exit_thread = false;
/// <summary>
/// 暫??刂?
/// </summary>
private bool pause_thread = false;
/// <summary>
/// 視頻輸出流videoindex
/// </summary>
private int videoindex = -1;
/// <summary>
/// 音頻輸出流audioindex
/// </summary>
private int audioindex = -1;
private bool isInit = false;
int error;
AVFormatContext* ofmt_ctx = null;
AVPacket* packet;
AVCodecContext* pCodecCtx_Video;
AVCodec* pCodec_Video;
AVFrame* pFrame_Video;
AVFrame* pFrameYUV_Video;
SwsContext* sws_ctx_video;
SDLHelper sdlVideo;
SDLAudio sdlAudio;
int out_buffer_size_video;
byte* out_buffer_video;
int video_frame_count, audio_frame_count;
AVCodecContext* pCodeCtx_Audio;
AVCodec* pCodec_Audio;
AVFrame* frame_Audio;
SwrContext* swrCtx_Audio;
byte* out_buffer_audio;
int out_buffer_size_audio;
int out_channel_nb;
AVSampleFormat out_sample_fmt;
/// <summary>
/// 初始化
/// </summary>
/// <param name="fileName"></param>
/// <param name="sdlVideo"></param>
/// <param name="sdlAudio"></param>
/// <returns></returns>
public int Init(string fileName, SDLHelper sdlVideo, SDLAudio sdlAudio)
{
AVFormatContext* ofmt_ctx;
// 注冊(cè)編解碼器
ffmpeg.avcodec_register_all();
// 獲取文件信息上下文初始化
ofmt_ctx = ffmpeg.avformat_alloc_context();
this.ofmt_ctx = ofmt_ctx;
// 打開媒體文件
error = ffmpeg.avformat_open_input(&ofmt_ctx, fileName, null, null);
if (error != 0)
{
throw new ApplicationException(FFmpegBinariesHelper.GetErrorMessage(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 (ofmt_ctx->streams[i]->codec->codec_type == AVMediaType.AVMEDIA_TYPE_AUDIO)
{
audioindex = i;
Console.WriteLine("audio.............." + audioindex);
}
}
if (videoindex == -1)
{
Console.WriteLine("Couldn't find a video stream.(沒(méi)有找到視頻流)");
return -1;
}
if (audioindex == -1)
{
Console.WriteLine("Couldn't find a audio stream.(沒(méi)有找到音頻流)");
return -1;
}
#region 初始化視頻
// 視頻流處理
if (videoindex > -1)
{
//獲取視頻流中的編解碼上下文
pCodecCtx_Video = ofmt_ctx->streams[videoindex]->codec;
//根據(jù)編解碼上下文中的編碼id查找對(duì)應(yīng)的解碼
pCodec_Video = ffmpeg.avcodec_find_decoder(pCodecCtx_Video->codec_id);
if (pCodec_Video == null)
{
Console.WriteLine("沒(méi)有找到編碼器");
return -1;
}
//打開編碼器
if (ffmpeg.avcodec_open2(pCodecCtx_Video, pCodec_Video, null) < 0)
{
Console.WriteLine("編碼器無(wú)法打開");
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_Video->width;
var height = pCodecCtx_Video->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_Video->name->ToString());
//準(zhǔn)備讀取
//AVPacket用于存儲(chǔ)一幀一幀的壓縮數(shù)據(jù)(H264)
//AVFrame用于存儲(chǔ)解碼后的像素?cái)?shù)據(jù)(YUV)
//內(nèi)存分配
pFrame_Video = ffmpeg.av_frame_alloc();
//YUV420
pFrameYUV_Video = ffmpeg.av_frame_alloc();
//只有指定了AVFrame的像素格式、畫面大小才能真正分配內(nèi)存
//緩沖區(qū)分配內(nèi)存
out_buffer_size_video = ffmpeg.avpicture_get_size(AVPixelFormat.AV_PIX_FMT_YUV420P, pCodecCtx_Video->width, pCodecCtx_Video->height);
out_buffer_video = (byte*)ffmpeg.av_malloc((ulong)out_buffer_size_video);
//初始化緩沖區(qū)
ffmpeg.avpicture_fill((AVPicture*)pFrameYUV_Video, out_buffer_video, AVPixelFormat.AV_PIX_FMT_YUV420P, pCodecCtx_Video->width, pCodecCtx_Video->height);
//用于轉(zhuǎn)碼(縮放)的參數(shù),轉(zhuǎn)之前的寬高,轉(zhuǎn)之后的寬高,格式等
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);
}
#endregion
#region 初始化音頻
// 音頻流處理
if (audioindex > -1)
{
//根據(jù)索引拿到對(duì)應(yīng)的流,根據(jù)流拿到解碼器上下文
pCodeCtx_Audio = ofmt_ctx->streams[audioindex]->codec;
//再根據(jù)上下文拿到編解碼id,通過(guò)該id拿到解碼器
pCodec_Audio = ffmpeg.avcodec_find_decoder(pCodeCtx_Audio->codec_id);
if (pCodec_Audio == null)
{
Console.WriteLine("沒(méi)有找到編碼器");
return -1;
}
//打開編碼器
if (ffmpeg.avcodec_open2(pCodeCtx_Audio, pCodec_Audio, null) < 0)
{
Console.WriteLine("編碼器無(wú)法打開");
return -1;
}
Console.WriteLine("Find a audio stream. channel=" + audioindex);
//解壓縮數(shù)據(jù)
frame_Audio = ffmpeg.av_frame_alloc();
//frame->16bit 44100 PCM 統(tǒng)一音頻采樣格式與采樣率
swrCtx_Audio = ffmpeg.swr_alloc();
//重采樣設(shè)置選項(xiàng)-----------------------------------------------------------start
//輸入的采樣格式
AVSampleFormat in_sample_fmt = pCodeCtx_Audio->sample_fmt;
//輸出的采樣格式 16bit PCM
out_sample_fmt = AVSampleFormat.AV_SAMPLE_FMT_S16;
//輸入的采樣率
int in_sample_rate = pCodeCtx_Audio->sample_rate;
//輸出的采樣率
int out_sample_rate = 44100;
//輸入的聲道布局
long in_ch_layout = (long)pCodeCtx_Audio->channel_layout;
//輸出的聲道布局
int out_ch_layout = ffmpeg.AV_CH_LAYOUT_MONO;
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);
ffmpeg.swr_init(swrCtx_Audio);
//重采樣設(shè)置選項(xiàng)-----------------------------------------------------------end
//獲取輸出的聲道個(gè)數(shù)
out_channel_nb = ffmpeg.av_get_channel_layout_nb_channels((ulong)out_ch_layout);
//存儲(chǔ)pcm數(shù)據(jù)
out_buffer_audio = (byte*)ffmpeg.av_malloc(2 * 44100);
}
#endregion
//緩沖區(qū),開辟空間
packet = (AVPacket*)ffmpeg.av_malloc((ulong)sizeof(AVPacket));
// 設(shè)置SDL播放對(duì)象
this.sdlVideo = sdlVideo;
this.sdlAudio = sdlAudio;
isInit = true;
return 0;
}
/// <summary>
/// 讀取音視頻流文件并進(jìn)行播放
/// </summary>
public unsafe int ReadAndPlay()
{
IsRun = true;
exit_thread = false;
pause_thread = false;
thread = Thread.CurrentThread;
//int error, frame_count = 0;
int got_frame, ret;
//SwsContext* pSwsCtx = null;
byte* out_audio_buffer = out_buffer_audio;
try
{
while (ffmpeg.av_read_frame(ofmt_ctx, packet) >= 0)
{
// 退出線程
if (exit_thread)
{
break;
}
// 暫停解析
while (pause_thread)
{
Thread.Sleep(100);
}
#region 視頻H264轉(zhuǎn)YUV并使用SDL進(jìn)行播放
if (packet->stream_index == videoindex)
{
//解碼一幀視頻壓縮數(shù)據(jù),得到視頻像素?cái)?shù)據(jù)
ret = ffmpeg.avcodec_decode_video2(pCodecCtx_Video, pFrame_Video, &got_frame, packet);
if (ret < 0)
{
Console.WriteLine("視頻解碼錯(cuò)誤");
return -1;
}
// 讀取解碼后的幀數(shù)據(jù)
if (got_frame > 0)
{
double pts = 0; //ffmpeg.av_frame_get_best_effort_timestamp(pFrameYUV_Video);
//VideoState* vs = null;
//vs->video_clock = pts;
//vs->video_st = ofmt_ctx->streams[videoindex];
//pts = synchronize_video(vs, pFrame_Video, pts);
//if (queue_picture(is, pFrame, pts) < 0)
//{
// break;
//}
video_frame_count++;
//存在問(wèn)題的PTS計(jì)算
//int pts = video_frame_count++ * (pCodecCtx_Video->pkt_timebase.num * 1000 / 25 /* pCodecCtx->pkt_timebase.den*/);
Console.WriteLine("視頻幀數(shù):第 " + video_frame_count + " 幀");
//AVFrame轉(zhuǎn)為像素格式Y(jié)UV420,寬高
ffmpeg.sws_scale(sws_ctx_video, pFrame_Video->data, pFrame_Video->linesize, 0, pCodecCtx_Video->height, pFrameYUV_Video->data, pFrameYUV_Video->linesize);
Console.WriteLine("視頻: pts= " + packet->pts + " dts=" + packet->dts);
// SDL播放YUV數(shù)據(jù):下面兩種方式都可以進(jìn)行播放
sdlVideo.SDL_Display(pCodecCtx_Video->width, pCodecCtx_Video->height, (IntPtr)out_buffer_video, out_buffer_size_video, pFrameYUV_Video->linesize[0]);
//sdlVideo.SDL_Display(pCodecCtx_Video->width, pCodecCtx_Video->height, (IntPtr)pFrameYUV_Video->data[0], out_buffer_size_video, pFrameYUV_Video->linesize[0]);
//DeleyToPlay_Video(packet->pts);
}
}
#endregion
#region 音頻AAC轉(zhuǎn)PCM并使用SDL進(jìn)行播放
if (packet->stream_index == audioindex)
{
//解碼AVPacket->AVFrame
ret = ffmpeg.avcodec_decode_audio4(pCodeCtx_Audio, frame_Audio, &got_frame, packet);
if (ret < 0)
{
Console.WriteLine("音頻解碼失敗");
return -1;
}
// 讀取幀數(shù)據(jù)
if (got_frame > 0)
{
audio_frame_count++;
Console.WriteLine("音頻幀數(shù):第 " + audio_frame_count + " 幀");
// 變換音頻
ffmpeg.swr_convert(swrCtx_Audio, &out_audio_buffer, 2 * 44100, (byte**)&frame_Audio->data, frame_Audio->nb_samples);
// 獲取sample的size
out_buffer_size_audio = ffmpeg.av_samples_get_buffer_size(null, out_channel_nb, frame_Audio->nb_samples, out_sample_fmt, 1);
Console.WriteLine("音頻: pts= " + packet->pts + " dts=" + packet->dts);
// SDL進(jìn)行音頻播放
sdlAudio.PlayAudio((IntPtr)out_audio_buffer, out_buffer_size_audio);
//DeleyToPlay_Audio(packet->pts);
}
}
#endregion
Thread.Sleep(20);
//釋放資源
ffmpeg.av_free_packet(packet);
}
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
finally
{
//if (&ofmt_ctx != null)
//{
// ffmpeg.avformat_close_input(&ofmt_ctx);//關(guān)閉流文件
//}
}
IsRun = false;
return 0;
}
/// <summary>
/// 開啟線程
/// </summary>
/// <param name="fileName"></param>
/// <param name="sdlVideo"></param>
/// <param name="sdlAudio"></param>
public void Start()
{
if (!isInit)
{
MessageBox.Show("沒(méi)有初始化");
}
thread = new Thread(() =>
{
try
{
ReadAndPlay();
}
catch (Exception ex)
{
SQ.Base.ErrorLog.WriteLog4Ex("JT1078CodecForMp4.Run Video", ex);
}
});
thread.IsBackground = true;
thread.Start();
}
/// <summary>
/// 暫停繼續(xù)
/// </summary>
public void GoOnPlay()
{
pause_thread = false;
sdlVideo.PlayVideo();
sdlAudio.PlayAudio();
}
/// <summary>
/// 暫停
/// </summary>
public void Pause()
{
pause_thread = true;
sdlVideo.PauseVideo();
sdlAudio.PauseAudio();
}
/// <summary>
/// 停止
/// </summary>
public void Stop()
{
exit_thread = true;
}
long lastPts_Video = 0;
DateTime lastTS_Video;
long lastPts_Audio = 0;
DateTime lastTS_Audio;
private void DeleyToPlay_Video(long pts)
{
if (lastPts_Video > 0 && lastTS_Video != null)
{
double delay = (DateTime.Now - lastTS_Video).TotalMilliseconds;
var i = (int)(pts - lastPts_Video - delay);
if (i >= 1)
{
Thread.Sleep(i);
}
}
lastTS_Video = DateTime.Now;
lastPts_Video = pts;
}
private void DeleyToPlay_Audio(long pts)
{
if (lastPts_Audio > 0 && lastTS_Audio != null)
{
double delay = (DateTime.Now - lastTS_Audio).TotalMilliseconds;
var i = (int)(pts - lastPts_Audio - delay);
if (i >= 1)
{
Thread.Sleep(i);
}
}
lastTS_Audio = DateTime.Now;
lastPts_Audio = pts;
}
# http://dranger.com/ffmpeg/tutorial05.html
//public struct VideoState
//{
// public double video_clock; // pts of last decoded frame / predicted pts of next decoded frame
// public AVStream* video_st;// video stream
//}
//public unsafe double synchronize_video(VideoState* vs, AVFrame* src_frame, double pts)
//{
// double frame_delay;
// if (pts != 0)
// {
// /* if we have pts, set video clock to it */
// vs->video_clock = pts;
// }
// else
// {
// /* if we aren't given a pts, set it to the clock */
// pts = vs->video_clock;
// }
// /* update the video clock */
// frame_delay = av_q2d(vs->video_st->codec->time_base);
// /* if we are repeating a frame, adjust clock accordingly */
// frame_delay += src_frame->repeat_pict * (frame_delay * 0.5);
// vs->video_clock += frame_delay;
// return pts;
//}
//struct VideoPicture
//{
// double pts;
//}
//int queue_picture(VideoState* vs, AVFrame* pFrame, double pts)
//{
// if (vp->bmp)
// {
// ... convert picture ...
// vp->pts = pts;
// ... alert queue ...
// }
//}
}
解決音視頻同步問(wèn)題版本
using CV.Media.Utils.Filter;
using CV.Video.Base;
using CV.Video.Base.FFmpeg;
using FFmpeg.AutoGen;
using JX;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using static CvNetVideo.UCVideo;
namespace CvNetVideo.Codec.Video
{
/// <summary>
/// MP4播放(音視頻使用同一個(gè)線程)
/// </summary>
public unsafe class JT1078CodecToPlayMp4
{
/// <summary>
/// 指示當(dāng)前解碼是否在運(yùn)行
/// </summary>
public bool IsRun { get; protected set; }
/// <summary>
/// 指示當(dāng)前解碼是否在暫停
/// </summary>
public bool IsPause { get; protected set; }
/// <summary>
/// 當(dāng)前線程
/// </summary>
public Thread thread;
/// <summary>
/// 退出控制
/// </summary>
private bool exit_thread = false;
/// <summary>
/// 暫??刂?
/// </summary>
private bool pause_thread = false;
/// <summary>
/// 視頻輸出流videoindex
/// </summary>
private int videoindex = -1;
/// <summary>
/// 音頻輸出流audioindex
/// </summary>
private int audioindex = -1;
/// <summary>
/// 是否初始化
/// </summary>
private bool isInit = false;
int error;
AVFormatContext* ofmt_ctx = null;
AVPacket* packet;
AVCodecContext* pCodecCtx_Video;
AVCodec* pCodec_Video;
AVFrame* pFrame_Video;
AVFrame* pFrameYUV_Video;
SwsContext* sws_ctx_video;
SDLHelper sdlVideo;
SDLAudio sdlAudio;
int out_buffer_size_video;
byte* out_buffer_video;
int video_frame_count, audio_frame_count;
AVCodecContext* pCodeCtx_Audio;
AVCodec* pCodec_Audio;
AVFrame* frame_Audio;
SwrContext* swrCtx_Audio;
byte* out_buffer_audio;
int out_buffer_size_audio;
int out_channel_nb;
AVSampleFormat out_sample_fmt;
int contrast;// 對(duì)比度
int brightness;// 亮度
int contrast_last;// 對(duì)比度
int brightness_last;// 亮度
//對(duì)比度亮度
private VideoFiltering m_video_filtering = new VideoFiltering();
/// <summary>
/// 設(shè)置圖像對(duì)比度和亮度
/// </summary>
/// <param name="contrast"></param>
/// <param name="brightness"></param>
/// <returns></returns>
public void SetContrastAndBrightness(int contrast, int brightness)
{
this.contrast = contrast;
this.brightness = brightness;
}
/// <summary>
/// YUV寬度
/// </summary>
public int YuvWidth { get; set; }
/// <summary>
/// YUV高度
/// </summary>
public int YuvHeight { get; set; }
/// <summary>
/// 記錄上一幀數(shù)據(jù)
/// </summary>
List<AVVideo> list = new List<AVVideo>();
/// <summary>
/// 初始化
/// </summary>
/// <param name="fileName"></param>
/// <param name="sdlVideo"></param>
/// <param name="sdlAudio"></param>
/// <returns></returns>
public int Init(string fileName, SDLHelper sdlVideo, SDLAudio sdlAudio)
{
AVFormatContext* ofmt_ctx;
// 注冊(cè)編解碼器
ffmpeg.avcodec_register_all();
// 獲取文件信息上下文初始化
ofmt_ctx = ffmpeg.avformat_alloc_context();
this.ofmt_ctx = ofmt_ctx;
// 打開媒體文件
error = ffmpeg.avformat_open_input(&ofmt_ctx, fileName, null, null);
if (error != 0)
{
throw new ApplicationException(FFmpegBinariesHelper.GetErrorMessage(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 (ofmt_ctx->streams[i]->codec->codec_type == AVMediaType.AVMEDIA_TYPE_AUDIO)
{
audioindex = i;
Console.WriteLine("audio.............." + audioindex);
}
}
if (videoindex == -1)
{
Console.WriteLine("Couldn't find a video stream.(沒(méi)有找到視頻流)");
return -1;
}
if (audioindex == -1)
{
Console.WriteLine("Couldn't find a audio stream.(沒(méi)有找到音頻流)");
return -1;
}
#region 初始化視頻
// 視頻流處理
if (videoindex > -1)
{
//獲取視頻流中的編解碼上下文
pCodecCtx_Video = ofmt_ctx->streams[videoindex]->codec;
//根據(jù)編解碼上下文中的編碼id查找對(duì)應(yīng)的解碼
pCodec_Video = ffmpeg.avcodec_find_decoder(pCodecCtx_Video->codec_id);
if (pCodec_Video == null)
{
Console.WriteLine("沒(méi)有找到編碼器");
return -1;
}
//打開編碼器
if (ffmpeg.avcodec_open2(pCodecCtx_Video, pCodec_Video, null) < 0)
{
Console.WriteLine("編碼器無(wú)法打開");
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_Video->width;
var height = pCodecCtx_Video->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_Video->name->ToString());
//準(zhǔn)備讀取
//AVPacket用于存儲(chǔ)一幀一幀的壓縮數(shù)據(jù)(H264)
//AVFrame用于存儲(chǔ)解碼后的像素?cái)?shù)據(jù)(YUV)
//內(nèi)存分配
pFrame_Video = ffmpeg.av_frame_alloc();
//YUV420
pFrameYUV_Video = ffmpeg.av_frame_alloc();
//只有指定了AVFrame的像素格式、畫面大小才能真正分配內(nèi)存
//緩沖區(qū)分配內(nèi)存
out_buffer_size_video = ffmpeg.avpicture_get_size(AVPixelFormat.AV_PIX_FMT_YUV420P, pCodecCtx_Video->width, pCodecCtx_Video->height);
out_buffer_video = (byte*)ffmpeg.av_malloc((ulong)out_buffer_size_video);
//初始化緩沖區(qū)
ffmpeg.avpicture_fill((AVPicture*)pFrameYUV_Video, out_buffer_video, AVPixelFormat.AV_PIX_FMT_YUV420P, pCodecCtx_Video->width, pCodecCtx_Video->height);
//用于轉(zhuǎn)碼(縮放)的參數(shù),轉(zhuǎn)之前的寬高,轉(zhuǎn)之后的寬高,格式等
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);
}
#endregion
#region 初始化音頻
// 音頻流處理
if (audioindex > -1)
{
//根據(jù)索引拿到對(duì)應(yīng)的流,根據(jù)流拿到解碼器上下文
pCodeCtx_Audio = ofmt_ctx->streams[audioindex]->codec;
//再根據(jù)上下文拿到編解碼id,通過(guò)該id拿到解碼器
pCodec_Audio = ffmpeg.avcodec_find_decoder(pCodeCtx_Audio->codec_id);
if (pCodec_Audio == null)
{
Console.WriteLine("沒(méi)有找到編碼器");
return -1;
}
//打開編碼器
if (ffmpeg.avcodec_open2(pCodeCtx_Audio, pCodec_Audio, null) < 0)
{
Console.WriteLine("編碼器無(wú)法打開");
return -1;
}
Console.WriteLine("Find a audio stream. channel=" + audioindex);
//解壓縮數(shù)據(jù)
frame_Audio = ffmpeg.av_frame_alloc();
//frame->16bit 8000 PCM 統(tǒng)一音頻采樣格式與采樣率
swrCtx_Audio = ffmpeg.swr_alloc();
//重采樣設(shè)置選項(xiàng)-----------------------------------------------------------start
//輸入的采樣格式
AVSampleFormat in_sample_fmt = pCodeCtx_Audio->sample_fmt;
//輸出的采樣格式 16bit PCM
out_sample_fmt = AVSampleFormat.AV_SAMPLE_FMT_S16;
//輸入的采樣率
int in_sample_rate = pCodeCtx_Audio->sample_rate;
//輸出的采樣率
int out_sample_rate = 8000;
//輸入的聲道布局
long in_ch_layout = (long)pCodeCtx_Audio->channel_layout;
//輸出的聲道布局
int out_ch_layout = ffmpeg.AV_CH_LAYOUT_MONO;
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);
ffmpeg.swr_init(swrCtx_Audio);
//重采樣設(shè)置選項(xiàng)-----------------------------------------------------------end
//獲取輸出的聲道個(gè)數(shù)
out_channel_nb = ffmpeg.av_get_channel_layout_nb_channels((ulong)out_ch_layout);
//存儲(chǔ)pcm數(shù)據(jù)
out_buffer_audio = (byte*)ffmpeg.av_malloc(2 * 8000);
}
#endregion
//緩沖區(qū),開辟空間
packet = (AVPacket*)ffmpeg.av_malloc((ulong)sizeof(AVPacket));
// 設(shè)置SDL播放對(duì)象
this.sdlVideo = sdlVideo;
this.sdlAudio = sdlAudio;
isInit = true;
return 0;
}
/// <summary>
/// 讀取音視頻流文件并進(jìn)行播放
/// </summary>
public unsafe int ReadAndPlay(PlayFinishedDo playFinishedDo)
{
IsRun = true;
exit_thread = false;
pause_thread = false;
thread = Thread.CurrentThread;
//int error, frame_count = 0;
int got_frame, ret;
//SwsContext* pSwsCtx = null;
byte* out_audio_buffer = out_buffer_audio;
try
{
AVStream* video_stream = ofmt_ctx->streams[videoindex];
while (ffmpeg.av_read_frame(ofmt_ctx, packet) >= 0&& !exit_thread)
{
// 暫停解析
while (pause_thread||isLastFrame)
{
// 退出線程
if (exit_thread)
{
break;
}
Thread.Sleep(10);
}
// 退出線程
if (exit_thread)
{
break;
}
// 此處記錄視頻的第一幀和第一幀的開始時(shí)間
if (firstPts == -1 && packet->stream_index == videoindex)
{
firstPts = packet->pts * 1000 / (video_stream->time_base.den / video_stream->time_base.num);
startTS = DateTime.Now;
}
// 針對(duì)視頻做延時(shí)播放,音頻自然播放就行不做處理
if (packet->stream_index == videoindex)
{
long pts_1 = packet->pts * 1000 / (video_stream->time_base.den / video_stream->time_base.num);
DeleyToPlay(pts_1);
}
#region 視頻H264轉(zhuǎn)YUV并使用SDL進(jìn)行播放
if (packet->stream_index == videoindex)
{
//解碼一幀視頻壓縮數(shù)據(jù),得到視頻像素?cái)?shù)據(jù)
ret = ffmpeg.avcodec_decode_video2(pCodecCtx_Video, pFrame_Video, &got_frame, packet);
if (ret < 0)
{
Console.WriteLine("視頻解碼錯(cuò)誤");
return -1;
}
//濾波,亮度,對(duì)比度===參考JT1078ToYuv -----------開始
int width = pCodecCtx_Video->width;
int height = pCodecCtx_Video->height;
if (contrast != contrast_last || brightness != brightness_last)
{
m_video_filtering.Reset(width, height, contrast, brightness);
contrast_last = contrast;
brightness_last = brightness;
}
//濾波,亮度,對(duì)比度===參考JT1078ToYuv -----------結(jié)束
// 讀取解碼后的幀數(shù)據(jù)
if (got_frame > 0)
{
video_frame_count++;
//>>>>濾波,亮度,對(duì)比度===參考JT1078ToYuv -----------開始
AVFrame* frame_filter;
ret = m_video_filtering.Filter(pFrame_Video, &frame_filter);
//>>>>濾波,亮度,對(duì)比度===參考JT1078ToYuv -----------結(jié)束
//AVFrame轉(zhuǎn)為像素格式Y(jié)UV420,寬高
ffmpeg.sws_scale(sws_ctx_video, frame_filter->data, frame_filter->linesize, 0, pCodecCtx_Video->height, pFrameYUV_Video->data, pFrameYUV_Video->linesize);
// 記錄上一幀圖像保持10個(gè)幀數(shù)
AVVideo videoFrame = new AVVideo(pCodecCtx_Video->width, pCodecCtx_Video->height, (IntPtr)out_buffer_video, out_buffer_size_video, pFrameYUV_Video->linesize[0]);
list.Add(videoFrame);
if (list.Count > 10) list.RemoveAt(0);
// SDL播放YUV數(shù)據(jù):下面兩種方式都可以進(jìn)行播放
sdlVideo.SDL_Display(pCodecCtx_Video->width, pCodecCtx_Video->height,YuvWidth, YuvHeight, (IntPtr)out_buffer_video, out_buffer_size_video, pFrameYUV_Video->linesize[0]);
//sdlVideo.SDL_Display(pCodecCtx_Video->width, pCodecCtx_Video->height, (IntPtr)pFrameYUV_Video->data[0], out_buffer_size_video, pFrameYUV_Video->linesize[0]);
// 播放下一幀時(shí)進(jìn)行暫停
if (isNextFrame)
{
Pause();
isNextFrame = false;
}
// 釋放濾波
m_video_filtering.UnrefFrame();
}
}
#endregion
#region 音頻AAC轉(zhuǎn)PCM并使用SDL進(jìn)行播放
if (packet->stream_index == audioindex)
{
//解碼AVPacket->AVFrame
ret = ffmpeg.avcodec_decode_audio4(pCodeCtx_Audio, frame_Audio, &got_frame, packet);
if (ret < 0)
{
Console.WriteLine("音頻解碼失敗");
return -1;
}
// 讀取幀數(shù)據(jù)
if (got_frame > 0)
{
audio_frame_count++;
// 變換音頻
ffmpeg.swr_convert(swrCtx_Audio, &out_audio_buffer, 2 * 8000, (byte**)&frame_Audio->data, frame_Audio->nb_samples);
// 獲取sample的size
out_buffer_size_audio = ffmpeg.av_samples_get_buffer_size(null, out_channel_nb, frame_Audio->nb_samples, out_sample_fmt, 1);
// SDL進(jìn)行音頻播放
sdlAudio.PlayAudio((IntPtr)out_audio_buffer, out_buffer_size_audio);
}
}
#endregion
//釋放資源
ffmpeg.av_free_packet(packet);
Thread.Sleep(10);
}
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
finally
{
// 釋放文件流
ffmpeg.avformat_free_context(ofmt_ctx);
// 修改右鍵菜單回調(diào)函數(shù)
playFinishedDo.Invoke();
}
IsRun = false;
IsPause = false;
return 0;
}
bool isLastFrame = false;
bool isNextFrame = false;
bool playFastly = false;
bool playSlowly = false;
int play_speed = 1;
long firstPts = -1;
DateTime startTS;
/// <summary>
/// 控制快慢
/// </summary>
/// <param name="pts"></param>
/// <param name="speed"></param>
private void DeleyToPlay(long pts)
{
int delayTime = 0;
try
{
// 計(jì)算延時(shí)
double delay = (DateTime.Now - startTS).TotalMilliseconds;
var i = (int)(pts - firstPts - delay);
if (i >= 100)
{
delayTime = 40;
delayTime = ControlFastOrSlow(delayTime);
}
else if (i >= 300)
{
delayTime = 60;
delayTime = ControlFastOrSlow(delayTime);
}
else if (i >= 500)
{
delayTime = 100;
delayTime = ControlFastOrSlow(delayTime);
}
}
catch
{
Console.WriteLine("Counting delay time error ");
}
finally
{
Console.WriteLine("Counting delay time = " + delayTime+ " play_speed="+ play_speed);
if (delayTime > 0)
Thread.Sleep(delayTime);
}
}
/// <summary>
/// 控制快慢
/// </summary>
/// <param name="delayTime"></param>
private int ControlFastOrSlow(int delayTime)
{
if (playFastly)
{
// 快放
delayTime /= play_speed;
}
else if (playSlowly)
{
// 慢放
delayTime *= play_speed;
}
return delayTime;
}
/// <summary>
/// 開啟線程
/// </summary>
/// <param name="fileName"></param>
/// <param name="sdlVideo"></param>
/// <param name="sdlAudio"></param>
public void Start(PlayFinishedDo playFinishedDo)
{
if (!isInit)
{
MessageBox.Show("沒(méi)有初始化");
}
thread = new Thread(() =>
{
try
{
ReadAndPlay(playFinishedDo);
}
catch (Exception ex)
{
SQ.Base.ErrorLog.WriteLog4Ex("JT1078CodecForMp4.Run Video", ex);
}
});
thread.IsBackground = true;
thread.Start();
}
/// <summary>
/// 暫停繼續(xù)
/// </summary>
public void GoOnPlay()
{
// 重置第一幀pts,處理暫停后音視頻不同步
firstPts = -1;
// 繼續(xù)的相關(guān)操作和變量修改
pause_thread = false;
IsPause = pause_thread;
sdlVideo.PlayVideo();
sdlAudio.PlayAudio();
}
/// <summary>
/// 暫停
/// </summary>
public void Pause()
{
// 暫停的相關(guān)操作和變量修改
pause_thread = true;
IsPause = pause_thread;
sdlVideo.PauseVideo();
sdlAudio.PauseAudio();
}
/// <summary>
/// 停止
/// </summary>
public void Stop()
{
exit_thread = true;
if (thread != null && thread.IsAlive)
{
thread.Abort();
thread.Join();
thread = null;
}
}
/// <summary>
/// 快放
/// </summary>
public void PlayFast()
{
if (pause_thread)
{
// 激活播放
GoOnPlay();
}
if (playSlowly)
{
play_speed = 1;
playSlowly = false;
}
else
{
play_speed++;
}
playFastly = true;
}
/// <summary>
/// 慢放
/// </summary>
public void PlaySlow()
{
if (pause_thread)
{
// 激活播放
GoOnPlay();
}
if (playFastly)
{
play_speed = 1;
playFastly = false;
}
else
{
play_speed++;
}
playSlowly = true;
}
/// <summary>
/// 上一幀
/// </summary>
public void PlayLastFrame()
{
// 修改上一幀標(biāo)志
isLastFrame = true;
// 每點(diǎn)擊一次向前播一幀
if (list.Count>0)
{
Console.WriteLine("剩余播放幀:"+ list.Count);
// 激活播放
GoOnPlay();
AVVideo lastFrame = list.Last();
// 播放上一幀圖像
sdlVideo.SDL_Display(lastFrame.width, lastFrame.height, lastFrame.pixels, lastFrame.pixelsSize, lastFrame.pitch);
// 修改上一幀標(biāo)志
isLastFrame = false;
// 移除已看過(guò)的幀
list.Remove(lastFrame);
Thread.Sleep(10);
Pause();
}
}
/// <summary>
/// 下一幀
/// </summary>
public void PlayNextFrame()
{
// 暫停以區(qū)分幀
Pause();
// 播放以完成下一幀圖像顯示或聲音播放
GoOnPlay();
// 下一幀播放完成暫停標(biāo)志
isNextFrame = true;
}
}
class Media
{
/// <summary>
/// 0:video,1:audio
/// </summary>
public int type { get; set; }
/// <summary>
/// pts value
/// </summary>
public long pts { get; set; }
}
class AVVideo : Media
{
public int width { get; set; }
public int height { get; set; }
public IntPtr pixels { get; set; }
public int pixelsSize { get; set; }
public int pitch { get; set; }
public AVVideo(int width, int height, IntPtr pixels, int pixelsSize, int pitch)
{
this.width = width;
this.height = height;
this.pixels = pixels;
this.pixelsSize = pixelsSize;
this.pitch = pitch;
}
}
class AVAudio : Media
{
public IntPtr pcm { get; set; }
public int len { get; set; }
public AVAudio(IntPtr pcm, int len)
{
this.pcm = pcm;
this.len = len;
}
}
}
以上這篇C# 使用SDL2實(shí)現(xiàn)Mp4文件播放音視頻操作就是小編分享給大家的全部?jī)?nèi)容了,希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
C#模擬實(shí)現(xiàn)抽獎(jiǎng)小程序的示例代碼
這篇文章主要介紹了通過(guò)C#模擬實(shí)現(xiàn)一個(gè)簡(jiǎn)單的抽獎(jiǎng)小程序,文中的示例代碼講解詳細(xì),對(duì)我們了解C#有一定的幫助,需要的可以參考一下2021-12-12
C# 對(duì)MongoDB 進(jìn)行增刪改查的簡(jiǎn)單操作實(shí)例
這篇文章介紹了C# 對(duì)MongoDB 進(jìn)行增刪改查的簡(jiǎn)單操作實(shí)例,有需要的朋友可以參考一下2013-09-09

