视频解码过程:
注册视频播放格式
打开文件(检测文件头部)
检测流
找到视频流的位置
通过视频流找到相应的解码器
查找解码器
打开解码器
初始化SDL库
创建用于显示视频的窗口
创建一个YUV覆盖
申请两个空间保存解码后的原帧和转换后的帧
从文件读取一个视频包
把包解码成帧
转换格式后输出
音频解码过程:
打开文件(检测文件头部)
检测流
找到音频流
通过音频流找到相应的解码器
查找解码器
打开解码器
初始化SDL库
设置采样率,通道数,回调函数,回调函数的参数
打开音频设备
获取音频转换信息
从文件读取一个音频包
把包解码成帧
转换格式后输出
#include <windows.h>
extern "C"{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
#include "libswresample/swresample.h"
#include "SDL.h"
#include "SDL_thread.h"
}
#define MAX_AUDIO_FRAME_SIZE 192000
#define SDL_AUDIO_BUFFER_SIZE 1024
struct Video_Fill { //ShowVideo_Fill线程参数结构体
bool flag, stop;
AVFrame *pFrameYUV;
AVCodecContext *pCodecCtx;
};
struct Audio_Fill{ //回调函数参数结构体
Uint8 *audio_chunk;
Uint32 audio_len;
Uint8 *audio_pos;
int out_nb_samples;
int out_channels;
int out_buffer_size;
uint8_t *out_buffer;
AVSampleFormat out_sample_fmt;
AVCodecContext *aCodecCtx;
SDL_AudioSpec *wanted_spec;
struct SwrContext *au_convert_ctx;
};
typedef struct PacketQueue { //包队列结构体
AVPacketList *first_pkt, *last_pkt;
int nb_packets;
int size;
SDL_mutex *mutex;
SDL_cond *cond;
} PacketQueue;
int quit = 0;
PacketQueue audioq; //音频包队列
PacketQueue videoq; //视频包队列
static void SaveFrame(AVFrame *pFrame, int width, int height, int pos)
{
FILE *pFile;
char szFilename[32];
int y;
sprintf(szFilename, "E:\\视频\\temp\\frame%d.ppm", pos);
pFile = fopen(szFilename, "wb");
if (!pFile)
return;
fprintf(pFile, "P6\n%d %d\n255\n", width, height);
for (y = 0; y < height; y++){
fwrite(pFrame->data[0] + y * pFrame->linesize[0], 1, width * 3, pFile);
}
fclose(pFile);
}
void packet_queue_init(PacketQueue *q) { //包队列初始化
memset(q, 0, sizeof(PacketQueue));
q->mutex = SDL_CreateMutex();
q->cond = SDL_CreateCond();
}
int packet_queue_put(PacketQueue *q, AVPacket *pkt) { //包入队
AVPacketList *pkt1;
if (av_dup_packet(pkt) < 0) {
return -1;
}
pkt1 = (AVPacketList *)av_malloc(sizeof(AVPacketList));
if (!pkt1)
return -1;
pkt1->pkt = *pkt;
pkt1->next = NULL;
SDL_LockMutex(q->mutex); //锁定队列,互斥的使用队列
if (!q->last_pkt){ //队列为空
q->first_pkt = pkt1;
}
else{
q->last_pkt->next = pkt1; //插入队尾, 入队
}
q->last_pkt = pkt1;
q->nb_packets++;
q->size += pkt1->pkt.size;
SDL_CondSignal(q->cond); //入队, 可以出队
SDL_UnlockMutex(q->mutex); //解锁都列
return 0;
}
static int packet_queue_get(PacketQueue *q, AVPacket *pkt, int block) { //包出队
AVPacketList *pkt1;
int ret;
SDL_LockMutex(q->mutex); //锁定队列,互斥的使用队列
while (true){
if (quit) {
ret = -1;
break;
}
pkt1 = q->first_pkt;
if (pkt1) { //队列不为空
q->first_pkt = pkt1->next;
if (!q->first_pkt)
q->last_pkt = NULL;
q->nb_packets--;
q->size -= pkt1->pkt.size;
*pkt = pkt1->pkt;
av_free(pkt1);
ret = 1;
break;
}
else if (!block) {
ret = 0;
break;
}
else {
SDL_CondWait(q->cond, q->mutex); //队列没有元素 等待。。。。
}
}
SDL_UnlockMutex(q->mutex); //解锁队列
return ret;
}
int audio_decode_frame(Audio_Fill *fill) { //音频解码函数
AVFrame *pFrame = NULL;
int got_picture;
AVPacket packet;
pFrame = av_frame_alloc();
packet_queue_get(&audioq, &packet, 1);
if (avcodec_decode_audio4(fill->aCodecCtx, pFrame, &got_picture, &packet) < 0){
//错误的音频流
return -1;
}
if (got_picture > 0){
swr_convert(fill->au_convert_ctx, &fill->out_buffer, MAX_AUDIO_FRAME_SIZE, (const uint8_t **)pFrame->data, pFrame->nb_samples);
if (fill->wanted_spec->samples != pFrame->nb_samples){
SDL_CloseAudio();
fill->out_nb_samples = pFrame->nb_samples;
fill->out_buffer_size = av_samples_get_buffer_size(NULL, fill->out_channels, fill->out_nb_samples, fill->out_sample_fmt, 1);
fill->wanted_spec->samples = fill->out_nb_samples;
SDL_OpenAudio(fill->wanted_spec, NULL);
}
fill->audio_chunk = (Uint8 *)fill->out_buffer;
fill->audio_len = fill->out_buffer_size;
fill->audio_pos = fill->audio_chunk;
}
}
void fill_audio(void *udata, Uint8 *stream, int len){ //音频回调函数
Audio_Fill *fill = (Audio_Fill*)udata;
audio_decode_frame(fill);
if (fill->audio_len == 0)
return;
len = (len > fill->audio_len ? fill->audio_len : len);
SDL_MixAudio(stream, fill->audio_pos, len, SDL_MIX_MAXVOLUME); //把声音数据写入stream
fill->audio_pos += len;
fill->audio_len -= len;
}
int video_decode_frame(Video_Fill *fill) //视频解码
{
AVFrame *pFrame;
AVPacket packet;
int frameFinished;
pFrame = av_frame_alloc();
//把包转换成帧
packet_queue_get(&videoq, &packet, 1);
avcodec_decode_video2(fill->pCodecCtx, pFrame, &frameFinished, &packet);
if (frameFinished){ //是否的到帧
//解码得到图像后,很有可能不是我们想要的 YUV420P 格式,因此需要使用 swscale 来做转换,
//调用 sws_getCachedContext 得到转换上下文,使用 sws_scale 将图形从解码后的格式转换为 YUV420P
struct SwsContext *img_convert_ctx = NULL;
//得到转换上下文
img_convert_ctx = sws_getCachedContext(img_convert_ctx, fill->pCodecCtx->width,
fill->pCodecCtx->height, fill->pCodecCtx->pix_fmt,
fill->pCodecCtx->width, fill->pCodecCtx->height,
AV_PIX_FMT_YUV420P, SWS_BICUBIC,
NULL, NULL, NULL
);
if (!img_convert_ctx){
//无法得到转换信息
return -1;
}
//转换成 YUV420P
sws_scale(img_convert_ctx, pFrame->data,
pFrame->linesize, 0, fill->pCodecCtx->height, fill->pFrameYUV->data,
fill->pFrameYUV->linesize);
fill->flag = true;
SDL_Delay(40);//25帧一秒
}
return 0;
}
DWORD WINAPI video_fill(void *ph) //视频播放线程
{
SDL_Surface *Screen = NULL; //显示图像的区域
SDL_Overlay *bmp = NULL;
SDL_Event event; //SDL窗口事件
SDL_Rect rect;
Video_Fill *fill = (Video_Fill*)ph;
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)){
//初始化失败
return -1;
}
//创建用于显示的屏幕
Screen = SDL_SetVideoMode(fill->pCodecCtx->width, fill->pCodecCtx->height, 0, 0);
if (!Screen){
//创建失败
return -1;
}
//创建一个YUV覆盖
bmp = SDL_CreateYUVOverlay(fill->pCodecCtx->width, fill->pCodecCtx->height, SDL_YV12_OVERLAY, Screen);
SDL_LockYUVOverlay(bmp);
for (int i = 0; i < 3; i++){
fill->pFrameYUV->data[i] = bmp->pixels[i];
fill->pFrameYUV->linesize[i] = bmp->pitches[i];
}
SDL_UnlockYUVOverlay(bmp);
rect.x = 0;
rect.y = 0;
rect.w = fill->pCodecCtx->width;
rect.h = fill->pCodecCtx->height;
while (fill->stop){
video_decode_frame(fill);
if (fill->flag){
fill->flag = false;
SDL_DisplayYUVOverlay(bmp, &rect);
}
SDL_PollEvent(&event); //从事件队列取出事件
switch (event.type){
case SDL_QUIT:
SDL_Quit();
exit(0);
break;
default:
break;
}
}
SDL_Quit();
return 0;
}
int main(int argc, char *argv[])
{
DWORD threadID;
HANDLE hthread;
AVFormatContext *pFormatCtx = NULL; //文件信息
int i, videoStream, audioStream; //视频流音频流位置
AVCodecContext *pCodecCtx = NULL; //视频解码器信息
AVCodecContext *aCodecCtx = NULL; //音频解码器信息
SDL_AudioSpec wanted_spec; //音频流信息
AVCodec *pCodec = NULL; //视频解码器
AVCodec *aCodec = NULL; //音频解码器
AVFrame *pFrame = NULL; //原始帧
AVFrame *pFrameYUV = NULL; //转换为 RGB24 的帧
AVPacket *packet = NULL; //保存从文件读取出来的一个包
int frameFinished;
int numBytes; //保存 RGB24 格式的图像需要占用的空间大小
uint8_t *buffer;
int got_picture;
Audio_Fill afill;
Video_Fill vfill;
av_register_all();
avformat_network_init();
packet_queue_init(&audioq);
packet_queue_init(&videoq);
argv[1] = "E:\\视频\\银河护卫队.rmvb";
packet = (AVPacket *)malloc(sizeof(AVPacket));
if (avformat_open_input(&pFormatCtx, argv[1], NULL, NULL)){
//打开文件失败
return -1;
}
if (avformat_find_stream_info(pFormatCtx, NULL) < 0){
//找不到流信息
return -1;
}
av_dump_format(pFormatCtx, 0, argv[1], 0); //输出文件信息
videoStream = -1;
audioStream = -1;
//找到第一条视频流
for (i = 0; i < pFormatCtx->nb_streams; i++){
if (videoStream < 0 && pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO){
videoStream = i;
}
if (audioStream < 0 && pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO){
audioStream = i;
}
}
if (videoStream == -1){
//没有找到视频流
return -1;
}
if (audioStream == -1){
//没有找到音频流
return -1;
}
pCodecCtx = pFormatCtx->streams[videoStream]->codec; //得到视频解码器信息
aCodecCtx = pFormatCtx->streams[audioStream]->codec; //得到音频解码器信息
pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
aCodec = avcodec_find_decoder(aCodecCtx->codec_id);
if (pCodec == NULL){
//找不到视频解码器
return -1;
}
if (aCodec == NULL){
//找不到音频解码器
return -1;
}
if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0){
//不能打开视频解码器
return -1;
}
if (avcodec_open2(aCodecCtx, aCodec, NULL) < 0){
//不能打开音频解码器
return -1;
}
pFrame = av_frame_alloc();
if (pFrame == NULL){
//分配失败
return -1;
}
pFrameYUV = av_frame_alloc();
if (pFrameYUV == NULL){
//分配失败
return -1;
}
//计算缓冲区大小
numBytes = avpicture_get_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height);
//为缓冲区分配内存
buffer = (uint8_t *)av_malloc(numBytes * sizeof(uint8_t));
//把帧和内存结合起来
avpicture_fill((AVPicture*)pFrameYUV, buffer, AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height);
uint64_t out_channel_layout = AV_CH_LAYOUT_STEREO;
//初始化SDL库
afill.audio_len = 0;
afill.aCodecCtx = aCodecCtx;
afill.out_nb_samples = 1024;
afill.out_sample_fmt = AV_SAMPLE_FMT_S16;
afill.out_channels = av_get_channel_layout_nb_channels(out_channel_layout);
afill.out_buffer_size = av_samples_get_buffer_size(NULL, afill.out_channels, afill.out_nb_samples, afill.out_sample_fmt, 1);
afill.out_buffer = (uint8_t *)av_malloc(MAX_AUDIO_FRAME_SIZE * 2);
afill.wanted_spec = &wanted_spec;
wanted_spec.freq = 44100;
wanted_spec.format = AUDIO_S16SYS;
wanted_spec.channels = afill.out_channels;
wanted_spec.silence = 0;
wanted_spec.samples = afill.out_nb_samples;
wanted_spec.callback = fill_audio;
wanted_spec.userdata = &afill;
if (SDL_OpenAudio(&wanted_spec, NULL) < 0){
//打开音频设备失败
return -1;
}
int64_t in_channel_layout = av_get_default_channel_layout(aCodecCtx->channels);
//音频转换信息
afill.au_convert_ctx = swr_alloc();
afill.au_convert_ctx = swr_alloc_set_opts(afill.au_convert_ctx, out_channel_layout, afill.out_sample_fmt, wanted_spec.freq,
in_channel_layout, aCodecCtx->sample_fmt, aCodecCtx->sample_rate, 0, NULL);
swr_init(afill.au_convert_ctx);
vfill.flag = false;
vfill.stop = true;
vfill.pCodecCtx = pCodecCtx;
vfill.pFrameYUV = pFrameYUV;
CloseHandle(hthread = CreateThread(NULL, 0, &video_fill, &vfill, 0, &threadID)); //显示线程
//从文件读取图片
SDL_PauseAudio(0);
while (av_read_frame(pFormatCtx, packet) >= 0){//读取一个包
if (packet->stream_index == videoStream){
packet_queue_put(&videoq, packet); //视频包入队
}
else if (packet->stream_index == audioStream){
packet_queue_put(&audioq, packet); //音频包入队
}
else{
av_free_packet(packet);
}
}
while (audioq.last_pkt != NULL || videoq.last_pkt != NULL)
SDL_Delay(10);
vfill.stop = false;
av_free(buffer);
av_free(pFrame);
av_free(pFrameYUV);
avcodec_close(pCodecCtx);
avformat_close_input(&pFormatCtx);
system("pause");
return 0;
}