Created
December 17, 2019 14:05
-
-
Save mashingan/e94d41e21c6e0ce4c9f19cea72a57dc4 to your computer and use it in GitHub Desktop.
Simplest example of creating player using FFMpeg and SDL2. Currently with choppy audio playing.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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; | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment