ffmpeg + sdl 视频播放器

视频解码过程:
注册视频播放格式
打开文件(检测文件头部)
检测流
找到视频流的位置
通过视频流找到相应的解码器
查找解码器
打开解码器
初始化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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

achonor

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值