100行代码实现最简单的基于FFMPEG+SDL的视频播放器(SDL1.x)

转自:

http://blog.csdn.net/leixiaohua1020/article/details/8652605

机器环境:

  • vs2010
  • SDL-1.2.15
  • ffmpeg-20160628-c0cb53c-win32-dev

代码:(代码略有修改, vs2010调试通过)

/**
 * 最简单的基于FFmpeg的视频播放器
 * Simplest FFmpeg Player
 *
 * 雷霄骅 Lei Xiaohua
 * [email protected]
 * 中国传媒大学/数字电视技术
 * Communication University of China / Digital TV Technology
 * http://blog.csdn.net/leixiaohua1020
 *
 * 本程序实现了视频文件的解码和显示(支持HEVC,H.264,MPEG2等)。
 * 是最简单的FFmpeg视频解码方面的教程。
 * 通过学习本例子可以了解FFmpeg的解码流程。
 * This software is a simplest video player based on FFmpeg.
 * Suitable for beginner of FFmpeg.
 */


#include 

//C99整数常量宏. [纯C程序可以不用, 而C++程序必须定义该宏.]
#define __STDC_CONSTANT_MACROS

// Windows平台
#ifdef _WIN32
// C++中包含C
extern "C"
{
#include "libavcodec/avcodec.h"     // ffmpeg编解码模块
#include "libavformat/avformat.h"   // ffmpeg视频格式模块
#include "libswscale/swscale.h"     // ffmpeg各种图像像素格式的转换,以及图像大小缩放
#include "SDL.h"                    // Simple DirectMedia Layer
};
#else
//Linux...
#ifdef __cplusplus                  // 将C++代码以标准C形式输出
extern "C"
{
#endif
#include 
#include 
#include 
#include 
#ifdef __cplusplus
};
#endif
#endif


//Full Screen
#define SHOW_FULLSCREEN 0
//Output YUV420P 
#define OUTPUT_YUV420P 0


int main(int argc, char* argv[])
{
    //FFmpeg
    AVFormatContext *pFormatCtx;        // 解封装功能结构体
    int             i, videoindex;
    AVCodecContext  *pCodecCtx;         // 解码功能结构体
    AVCodec         *pCodec;            // 解码器
    AVFrame *pFrame,*pFrameYUV;         // 解码后数据/帧
    AVPacket *packet;                   // 解码前数据
    struct SwsContext *img_convert_ctx; // 图像转换结构体
    //SDL
    int screen_w,screen_h;              // 屏幕的宽&高
    SDL_Surface *screen;                // Graphical Surface Structure
    SDL_VideoInfo *vi;                  // Video Target information
    SDL_Overlay *bmp;                   // YUV video overlay
    SDL_Rect rect;                      // Defines a rectangular area

    FILE *fp_yuv;                                   // 定义YUV文件
    int ret, got_picture;
    char filepath[]="some.mp4";    // 定义文件路径

    av_register_all();                              // ffmpeg注册复用器&编码器
    avformat_network_init();                        // ffmpeg网络流初始化

    pFormatCtx = avformat_alloc_context();          // 初始化解封装功能结构体AVFormatContext

    // 输入输出结构体AVIOContext的初始化
    if(avformat_open_input(&pFormatCtx,filepath,NULL,NULL)!=0){
        printf("Couldn't open input stream.\n");
        return -1;
    }

    // 给每个媒体流(音频/视频)的AVStream结构体赋值
    if(avformat_find_stream_info(pFormatCtx,NULL)<0){
        printf("Couldn't find stream information.\n");
        return -1;
    }

    // 获取所有视频流
    videoindex=-1;
    for(i=0; inb_streams; i++) 
        if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO){
            videoindex=i;
            break;
        }

    // 如果没有发现视频流,则返回
    if(videoindex==-1){
        printf("Didn't find a video stream.\n");
        return -1;
    }

    // 获取视频流的解码器
    pCodecCtx=pFormatCtx->streams[videoindex]->codec;

    // 通过code ID查找一个已经注册的音视频解码器
    // 查找解码器之前,必须先调用av_register_all注册所有支持的解码器
    pCodec=avcodec_find_decoder(pCodecCtx->codec_id);
    if(pCodec==NULL){
        printf("Codec not found.\n");
        return -1;
    }

    // 初始化一个视音频编解码器的AVCodecContext
    if(avcodec_open2(pCodecCtx, pCodec,NULL)<0){
        printf("Could not open codec.\n");
        return -1;
    }

    // 初始化解码后数据AVFrame
    pFrame=av_frame_alloc();
    pFrameYUV=av_frame_alloc();

    // SDL初如化(视频|音频|定时器)
    if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {  
        printf( "Could not initialize SDL - %s\n", SDL_GetError()); 
        return -1;
    } 

// 是否全屏
#if SHOW_FULLSCREEN
    vi = SDL_GetVideoInfo();
    screen_w = vi->current_w;
    screen_h = vi->current_h;
    screen = SDL_SetVideoMode(screen_w, screen_h, 0,SDL_FULLSCREEN);
#else
    screen_w = pCodecCtx->width;
    screen_h = pCodecCtx->height;
    // 创建一个窗口
    screen = SDL_SetVideoMode(screen_w, screen_h, 0,0);
#endif

    if(!screen) {  
        printf("SDL: could not set video mode - exiting:%s\n",SDL_GetError());  
        return -1;
    }

    // 创建YUV图像
    bmp = SDL_CreateYUVOverlay(pCodecCtx->width, pCodecCtx->height,SDL_YV12_OVERLAY, screen); 

    rect.x = 0;
    rect.y = 0;
    rect.w = screen_w;
    rect.h = screen_h;  
    //SDL End------------------------


    packet=(AVPacket *)av_malloc(sizeof(AVPacket));
    //Output Information-----------------------------
    printf("------------- File Information ------------------\n");

    // 解封装结构体赋值
    av_dump_format(pFormatCtx,0,filepath,0);
    printf("-------------------------------------------------\n");

#if OUTPUT_YUV420P 
fp_yuv=fopen("output.yuv","wb+");  
#endif  
    // 设置窗口标题
    SDL_WM_SetCaption("Simplest FFmpeg Player",NULL);

    // 初始化一个SwsContext
    img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL); 

    //-----------------------------------------------------------
    // av_read_frame读取码流中的音频若干帧或者视频一帧
    // 输入的AVFormatContext,输出的AVPacket
    while(av_read_frame(pFormatCtx, packet)>=0){
        if(packet->stream_index==videoindex){
            // 解码
            // 变量的定义:int ret, got_picture;
            // 解码一帧视频数据。输入一个解码前AVCodecContext,输出一个解码后的结构体AVFrame
            ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);
            if(ret < 0){
                printf("Decode Error.\n");
                return -1;
            }

            // got_picture为零表示获取失败,非零获取成功
            if(got_picture){
                // Lock an overlay
                SDL_LockYUVOverlay(bmp);

                // SDL_Overlay *bmp;
                // AVFrame  *pFrame,*pFrameYUV;
                // YUV420P中data[0]存Y,data[1]存U,data[2]存V
                // 这里为啥交换1,2顺序???
                pFrameYUV->data[0]=bmp->pixels[0];
                pFrameYUV->data[1]=bmp->pixels[2];
                pFrameYUV->data[2]=bmp->pixels[1]; 

                pFrameYUV->linesize[0]=bmp->pitches[0];
                pFrameYUV->linesize[1]=bmp->pitches[2];   
                pFrameYUV->linesize[2]=bmp->pitches[1];

                // struct SwsContext *img_convert_ctx;
                // sws_scale主要用于在2个AVFrame之间进行转换。
                sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, 
                    pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize);
#if OUTPUT_YUV420P
                int y_size=pCodecCtx->width*pCodecCtx->height;  
                // FILE *fp_yuv;
                // 将转换后的原始数据存成文件
                // 保存YUV420P格式的数据
                fwrite(pFrameYUV->data[0],1,y_size,fp_yuv);     //Y 
                fwrite(pFrameYUV->data[1],1,y_size/4,fp_yuv);   //U
                fwrite(pFrameYUV->data[2],1,y_size/4,fp_yuv);   //V
#endif
                // UnLock an overlay
                SDL_UnlockYUVOverlay(bmp); 

                // Blit the overlay to the display
                SDL_DisplayYUVOverlay(bmp, &rect); 

                // 设置延迟,最小10ms.
                // Delay 40ms
                SDL_Delay(40);
            }
        }

        // 释放AVPacket对象
        av_free_packet(packet);
    }

    //FIX: Flush Frames remained in Codec
    while (1) {
        // 解码一帧视频数据
        ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);
        if (ret < 0)
            break;
        if (!got_picture)
            break;

        sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize);

        SDL_LockYUVOverlay(bmp);
        pFrameYUV->data[0]=bmp->pixels[0];
        pFrameYUV->data[1]=bmp->pixels[2];
        pFrameYUV->data[2]=bmp->pixels[1]; 

        pFrameYUV->linesize[0]=bmp->pitches[0];
        pFrameYUV->linesize[1]=bmp->pitches[2];   
        pFrameYUV->linesize[2]=bmp->pitches[1];
#if OUTPUT_YUV420P
        int y_size=pCodecCtx->width*pCodecCtx->height;  
        fwrite(pFrameYUV->data[0],1,y_size,fp_yuv);//Y 
        fwrite(pFrameYUV->data[1],1,y_size/4,fp_yuv);  //U
        fwrite(pFrameYUV->data[2],1,y_size/4,fp_yuv);  //V
#endif

        SDL_UnlockYUVOverlay(bmp); 
        SDL_DisplayYUVOverlay(bmp, &rect); 
        //Delay 40ms
        SDL_Delay(40);
    }

    sws_freeContext(img_convert_ctx);

#if OUTPUT_YUV420P 
fclose(fp_yuv);
#endif 

    SDL_Quit();

    //av_free(out_buffer);
    av_free(pFrameYUV);
    avcodec_close(pCodecCtx);
    avformat_close_input(&pFormatCtx);

    return 0;
}

你可能感兴趣的:(100行代码实现最简单的基于FFMPEG+SDL的视频播放器(SDL1.x))