// 简单了解,写了一个播放器
#include<iostream>
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <SDL/SDL.h>
#include <SDL/SDL_image.h>
#include <SDL/SDL_thread.h>
}

using namespace std;

int main(int argc, char* argv[]) {

    // 注册了所有的文件格式和编解码的库,它们将被自动的使用在被打开的合适格式的文件上
    av_register_all();

    // AVFormatContext是FFMpeg格式转换过程中实现输入和输出功能、保存相关数据的主要结构。
    // 每一个输入和输出文件,都在如下定义的指针数组全局变量中有对应的实体。
    AVFormatContext *pFormatCtx;

    // 其中负责申请一个AVFormatContext结构的内存,并进行简单初始化
    // avformat_free_context()可以用来释放该结构里的所有东西以及该结构本身
    // 也是就说使用 avformat_alloc_context()分配的结构,需要使用avformat_free_context()来释放
    // 有些版本中函数名可能为: av_alloc_format_context();
    pFormatCtx = avformat_alloc_context();

    // 视频文件
    char filepath[] = "kaoya.mp4";

    // avformat_open_input 解析文件头
    // Open an input stream and read the header
    if (avformat_open_input(&pFormatCtx, filepath, NULL, NULL) != 0) {
    printf("Can't open the file\n");
    return -1;
    }

    // 调用 avformat_find_stream_info 解析文件中的流并得到流中一些必须的信息
    // Retrieve stream information
    if (avformat_find_stream_info(pFormatCtx, NULL) < 0) {
    printf("Couldn't find stream information.\n");
    return -1;
    }

    // output file information
    cout << "文件信息----------------------------------" << endl;
    av_dump_format(pFormatCtx, 0, filepath, 0);
    cout << "--------------------------------------------" << endl;

    // --------------------------------------------------------- //

    //Find the first video stream
    int i, videoIndex = -1;

    // 视音频流的个数
    for (i = 0; i < pFormatCtx->nb_streams; i++) {
    if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
        videoIndex = i;
        break;
    }
    }

    // 不存在则返回
    if (videoIndex == -1)
    return -1;

    // --------------------------------------------------------- //

    // 这是一个描述编解码器上下文的数据结构,包含了众多编解码器需要的参数信息
    AVCodecContext *pCodecCtx;

    // AVCodec是存储编解码器信息的结构体
    AVCodec *pCodec;

    // Get a pointer to the codec context for the video stream
    // 流中关于编解码器的信息就是被我们叫做"codec context"(编解码器上下文)
    // 的东西。这里面包含了流中所使用的关于编解码器的所有信息
    pCodecCtx = pFormatCtx->streams[videoIndex]->codec;

    //Find the decoder for the video stream
    pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
    if (pCodec == NULL) {
    printf("Unsupported codec!\n");
    return -1;
    }

    //Open codec
    if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
    printf("Could not open codec.\n");
    return -1;
    }

    //allocate video frame and set its fileds to default value
    AVFrame *pFrame, *pFrameYUV;
    pFrame = av_frame_alloc();
    pFrameYUV = av_frame_alloc();

    //即使我们申请了一帧的内存,当转换的时候,我们仍然需要一个地方来放置原始
    //的数据。我们使用avpicture_get_size 来获得我们需要的大小, 然后手工申请
    //内存空间:
    uint8_t *out_buffer;
    int numBytes;
    numBytes = avpicture_get_size(PIX_FMT_YUV420P, pCodecCtx->width,
        pCodecCtx->height);

    //av_malloc 是ffmpeg 的malloc,用来实现一个简单的malloc 的包装,这样来保
    //证内存地址是对齐的(4 字节对齐或者2 字节对齐)。它并不能保 护你不被内
    //存泄漏,重复释放或者其它malloc 的问题所困扰。
    out_buffer = (uint8_t *) av_malloc(numBytes * sizeof(uint8_t));

    //Assign appropriate parts of buffer to image planes in pFrameYUV
    //Note that pFrameYUV is an AVFrame, but AVFrame is a superset of AVPicture
    avpicture_fill((AVPicture*) pFrameYUV, out_buffer, PIX_FMT_YUV420P,
        pCodecCtx->width, pCodecCtx->height);

    //----------------SDL--------------------------------------//

    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
    printf("Could not initialize SDL -%s\n", SDL_GetError());
    exit(1);
    }

    SDL_Surface *screen;
    screen = SDL_SetVideoMode(pCodecCtx->width, pCodecCtx->height, 24, 0);
    if (!screen) {
    fprintf(stderr, "SDL: could not set video mode - exiting\n");
    exit(1);
    }

    SDL_Overlay *bmp;
    // Allocate a place to put our YUV image on that screen
    bmp = SDL_CreateYUVOverlay(pCodecCtx->width, pCodecCtx->height,
    SDL_YV12_OVERLAY, screen);

    SDL_Rect rect;
    rect.x = 0, rect.y = 0;
    rect.w = pCodecCtx->width;
    rect.h = pCodecCtx->height;

    //*************************************************************//

    //通过读取包来读取整个视频流,然后把它解码成帧,最后转换格式并且保存
    int frameFinished;

    AVPacket packet;
    av_new_packet(&packet, numBytes);

    int ret;
    // Convert the image into YUV format that SDL uses
    static struct SwsContext *img_convert_ctx;
    img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height,
        pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height,
        PIX_FMT_YUV420P,
        SWS_BICUBIC, NULL, NULL, NULL);

    // Read the next frame of a stream
    while (av_read_frame(pFormatCtx, &packet) >= 0) {

    // Is this a packet from the video stream?
    if (packet.stream_index == videoIndex) {

        // decode video frame of size packet.size from packet.data into picture
        ret = avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished,
            &packet);

        // Did we get a video frame?
        if (ret >= 0) {

        // Convert the image from its native format to YUV
        if (frameFinished) {

            SDL_LockYUVOverlay(bmp);

            AVPicture pict;
            pict.data[0] = bmp->pixels[0];
            pict.data[1] = bmp->pixels[2];
            pict.data[2] = bmp->pixels[1];

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

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

            SDL_UnlockYUVOverlay(bmp);

            SDL_DisplayYUVOverlay(bmp, &rect);

        }
        }
    }

    av_free_packet(&packet);

    SDL_Event event;
    SDL_PollEvent(&event);
    switch (event.type) {
    case SDL_QUIT:
        SDL_Quit();
        exit(0);
        break;
    default:
        break;
    }
    }

    av_frame_free(&pFrame);
    av_frame_free(&pFrameYUV);
    avcodec_close(pCodecCtx);
    avformat_close_input(&pFormatCtx);

    return 0;

}