Skip to content

Instantly share code, notes, and snippets.

@mashingan
Created December 17, 2019 14:05
Show Gist options
  • Save mashingan/e94d41e21c6e0ce4c9f19cea72a57dc4 to your computer and use it in GitHub Desktop.
Save mashingan/e94d41e21c6e0ce4c9f19cea72a57dc4 to your computer and use it in GitHub Desktop.

Revisions

  1. mashingan created this gist Dec 17, 2019.
    269 changes: 269 additions & 0 deletions simplest_player.c
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,269 @@
    #include <stdio.h>
    #include <stdlib.h>
    #include <stdbool.h>
    #include <time.h>
    #include <windows.h>
    #include <libavcodec/avcodec.h>
    #include <libavformat/avformat.h>
    //#include <libavutil/frame.h>
    #include <SDL2/SDL.h>

    /*
    ffmpeg path: d:/dev/libffmpeg
    sdl2 path: d:/dev/sdl2
    to compile it:
    gcc -Id:/dev/libffmpeg/include -Id:/dev/sdl2/include \
    -Ld:/dev/libffmpeg/lib -Ld:/dev/sdl2/lib \
    fplay.c -lavcodec -lavutil -lavformat -lmingw32 -lSDL2main -lSDL2
    */

    void display(AVCodecContext*, AVPacket*, AVFrame*, SDL_Rect*,
    SDL_Texture*, SDL_Renderer*, double);

    void playaudio(AVCodecContext *ctx, AVPacket *pkt, AVFrame *frame,
    SDL_AudioDeviceID auddev);

    int main(int argc, char *argv[]) {
    if (argc < 2) {
    printf("usage: %s <filename>\n", argv[0]);
    exit(EXIT_FAILURE);
    }
    // ffmpeg part
    AVFormatContext *pFormatCtx;
    int vidId = -1, audId = -1;
    double fpsrendering = 0.0;
    AVCodecContext *vidCtx, *audCtx;
    AVCodec *vidCodec, *audCodec;
    AVCodecParameters *vidpar, *audpar;
    AVFrame *vframe, *aframe;
    AVPacket *packet;

    //sdl part
    int swidth, sheight;
    SDL_Window *screen;
    SDL_Renderer *renderer;
    SDL_Texture *texture;
    SDL_Rect rect;
    SDL_AudioDeviceID auddev;
    SDL_AudioSpec want, have;

    SDL_Init(SDL_INIT_EVERYTHING);
    pFormatCtx = avformat_alloc_context();
    char bufmsg[1024];
    if (avformat_open_input(&pFormatCtx, argv[1], NULL, NULL) < 0) {
    sprintf(bufmsg, "Cannot open %s", argv[1]);
    perror(bufmsg);
    goto clean_format_context;
    }
    if (avformat_find_stream_info(pFormatCtx, NULL) < 0) {
    perror("Cannot find stream info. Quitting.");
    goto clean_format_context;
    }
    bool foundVideo = false, foundAudio = false;
    for (int i = 0; i < pFormatCtx->nb_streams; i++) {
    AVCodecParameters *localparam = pFormatCtx->streams[i]->codecpar;
    AVCodec *localcodec = avcodec_find_decoder(localparam->codec_id);
    if (localparam->codec_type == AVMEDIA_TYPE_VIDEO && !foundVideo) {
    vidCodec = localcodec;
    vidpar = localparam;
    vidId = i;
    AVRational rational = pFormatCtx->streams[i]->avg_frame_rate;
    fpsrendering = 1.0 / ((double)rational.num / (double)(rational.den));
    foundVideo = true;
    } else if (localparam->codec_type == AVMEDIA_TYPE_AUDIO && !foundAudio) {
    audCodec = localcodec;
    audpar = localparam;
    audId = i;
    foundAudio = true;
    }
    if (foundVideo && foundAudio) { break; }
    }
    vidCtx = avcodec_alloc_context3(vidCodec);
    audCtx = avcodec_alloc_context3(audCodec);
    if (avcodec_parameters_to_context(vidCtx, vidpar) < 0) {
    perror("vidCtx");
    goto clean_codec_context;
    }
    if (avcodec_parameters_to_context(audCtx, audpar) < 0) {
    perror("audCtx");
    goto clean_codec_context;
    }
    if (avcodec_open2(vidCtx, vidCodec, NULL) < 0) {
    perror("vidCtx");
    goto clean_codec_context;
    }
    if (avcodec_open2(audCtx, audCodec, NULL) < 0) {
    perror("audCtx");
    goto clean_codec_context;
    }

    vframe = av_frame_alloc();
    aframe = av_frame_alloc();
    packet = av_packet_alloc();
    swidth = vidpar->width;
    sheight = vidpar->height;
    screen = SDL_CreateWindow("Fplay", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
    swidth, sheight, SDL_WINDOW_OPENGL);
    if (!screen) {
    perror("screen");
    goto clean_packet_frame;
    }
    renderer = SDL_CreateRenderer(screen, -1, SDL_RENDERER_ACCELERATED);
    if (!renderer) {
    perror("renderer");
    goto clean_renderer;
    }
    texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_IYUV,
    SDL_TEXTUREACCESS_STREAMING | SDL_TEXTUREACCESS_TARGET,
    swidth, sheight);
    if (!texture) {
    perror("texture");
    goto clean_texture;
    }
    rect.x = 0;
    rect.y = 0;
    rect.w = swidth;
    rect.h = sheight;

    SDL_zero(want);
    SDL_zero(have);
    want.samples = audpar->sample_rate;
    want.channels = audpar->channels;
    auddev = SDL_OpenAudioDevice(NULL, 0, &want, &have, 0);
    SDL_PauseAudioDevice(auddev, 0);
    if (!auddev) {
    perror("auddev");
    goto clean_audio_device;
    }
    SDL_Event evt;
    uint32_t windowID = SDL_GetWindowID(screen);
    bool running = true;
    while (running) {
    while (av_read_frame(pFormatCtx, packet) >= 0)
    {
    while (SDL_PollEvent(&evt))
    {
    switch (evt.type) {
    case SDL_WINDOWEVENT: {
    if (evt.window.windowID == windowID) {
    switch (evt.window.event) {
    case SDL_WINDOWEVENT_CLOSE: {
    evt.type = SDL_QUIT;
    running = false;
    SDL_PushEvent(&evt);
    break;
    }
    };
    }
    break;
    }
    case SDL_QUIT: {
    running = false;
    break;
    }
    }
    }
    if (packet->stream_index == vidId) {
    display(vidCtx, packet, vframe, &rect,
    texture, renderer, fpsrendering);

    } else if (packet->stream_index == audId) {
    playaudio(audCtx, packet, aframe, auddev);

    }
    av_packet_unref(packet);
    }
    }


    clean_audio_device:
    SDL_CloseAudioDevice(auddev);
    clean_texture:
    SDL_DestroyTexture(texture);
    clean_renderer:
    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(screen);
    clean_packet_frame:
    av_packet_free(&packet);
    av_frame_free(&vframe);
    av_frame_free(&aframe);
    clean_codec_context:
    avcodec_free_context(&vidCtx);
    avcodec_free_context(&audCtx);
    clean_format_context:
    avformat_close_input(&pFormatCtx);
    avformat_free_context(pFormatCtx);

    SDL_Quit();

    return 0;
    }


    void display(AVCodecContext* ctx, AVPacket* pkt, AVFrame* frame, SDL_Rect* rect,
    SDL_Texture* texture, SDL_Renderer* renderer, double fpsrend)
    {
    time_t start = time(NULL);
    if (avcodec_send_packet(ctx, pkt) < 0) {
    perror("send packet");
    return;
    }
    if (avcodec_receive_frame(ctx, frame) < 0) {
    perror("receive frame");
    return;
    }
    int framenum = ctx->frame_number;
    if ((framenum % 1000) == 0) {
    printf("Frame %d (size=%d pts %d dts %d key_frame %d"
    " [ codec_picture_number %d, display_picture_number %d\n",
    framenum, frame->pkt_size, frame->pts, frame->pkt_dts, frame->key_frame,
    frame->coded_picture_number, frame->display_picture_number);
    }
    SDL_UpdateYUVTexture(texture, rect,
    frame->data[0], frame->linesize[0],
    frame->data[1], frame->linesize[1],
    frame->data[2], frame->linesize[2]);
    SDL_RenderClear(renderer);
    SDL_RenderCopy(renderer, texture, NULL, rect);
    SDL_RenderPresent(renderer);
    time_t end = time(NULL);
    double diffms = difftime(end, start) / 1000.0;
    if (diffms < fpsrend) {
    uint32_t diff = (uint32_t)((fpsrend - diffms) * 1000);
    printf("diffms: %f, delay time %d ms.\n", diffms, diff);
    SDL_Delay(diff);
    }
    }

    void playaudio(AVCodecContext *ctx, AVPacket *pkt, AVFrame *frame,
    SDL_AudioDeviceID auddev)
    {
    if (avcodec_send_packet(ctx, pkt) < 0) {
    perror("send packet");
    return;
    }
    if (avcodec_receive_frame(ctx, frame) < 0) {
    perror("receive frame");
    return;
    }
    int size;
    int bufsize = av_samples_get_buffer_size(&size, ctx->channels,
    frame->nb_samples, frame->format, 0);
    bool isplanar = av_sample_fmt_is_planar(frame->format) == 1;
    for (int ch = 0; ch < ctx->channels; ch++) {
    if (!isplanar) {
    if (SDL_QueueAudio(auddev, frame->data[ch], frame->linesize[ch]) < 0) {
    perror("playaudio");
    printf(" %s\n", SDL_GetError());
    return;
    }
    } else {
    if (SDL_QueueAudio(auddev, frame->data[0] + size*ch, size) < 0) {
    perror("playaudio");
    printf(" %s\n", SDL_GetError());
    return;
    }
    }
    }
    }