diff --git a/ffmpeg.c b/ffmpeg.c new file mode 100644 index 0000000..55ed105 --- /dev/null +++ b/ffmpeg.c @@ -0,0 +1,375 @@ + +#include + +#include +#include + +#include "ffmpeg.h" + +FFmpeg * +ff_new(void) +{ + FFmpeg *ff; + + ff = (FFmpeg*)calloc(1, sizeof(FFmpeg)); + if (!ff) + return NULL; + + ff->videos = eina_array_new(1); + if (!ff->videos) + goto free_ff; + + ff->audios = eina_array_new(1); + if (!ff->audios) + goto free_videos; + + return ff; + + free_videos: + eina_array_free(ff->videos); + free_ff: + free(ff); + + return NULL; +} + +void +ff_free(FFmpeg *ff) +{ + if (!ff) + return; + + eina_array_free(ff->audios); + eina_array_free(ff->videos); + free(ff); +} + +static void +_ff_free_lists(FFmpeg *ff) +{ + Eina_Array_Iterator iterator; + void *p; + unsigned int i; + + EINA_ARRAY_ITER_NEXT(ff->audios, i, p, iterator) + { + free(p); + } + eina_array_free(ff->audios); + + EINA_ARRAY_ITER_NEXT(ff->videos, i, p, iterator) + { + free(p); + } + eina_array_free(ff->videos); +} + +int +ff_file_open(FFmpeg *ff, const char *file) +{ + FF_Video *video; + int err; + unsigned int i; + + if ((!file || !*file)) + return 0; + + ff->format_ctx = avformat_alloc_context(); + if (!ff->format_ctx) + { + printf("ff NULL\n"); + return 0; + } + + ff->video_index = -1; + ff->audio_index = -1; + + /* parse header of the file */ + err = avformat_open_input(&ff->format_ctx, file, NULL, NULL); + if (err) + { + printf("avformat_open_input() failed"); + goto free_format_ctx; + } + + /* fill format_stx->streams */ + if (avformat_find_stream_info(ff->format_ctx, NULL) < 0) + { + printf("avformat_find_stream_info() failed"); + goto free_format_ctx; + } + + printf(" * file name : %s\n", ff->format_ctx->url); + printf(" * format name : %s\n", ff->format_ctx->iformat->name); + printf(" * duration : %lld\n", ff->format_ctx->duration); + printf(" * bitrate : %lld\n", ff->format_ctx->bit_rate); + + for (i = 0; i < ff->format_ctx->nb_streams; i++) + { + const AVCodecParameters *codec_par; + const AVCodec *codec; + + printf(" * stream %d time base : %d/%d\n", + i, + ff->format_ctx->streams[i]->time_base.num, + ff->format_ctx->streams[i]->time_base.den); + printf(" * stream %d r frame rate: %d/%d\n", + i, + ff->format_ctx->streams[i]->r_frame_rate.num, + ff->format_ctx->streams[i]->r_frame_rate.den); + printf(" * stream %d a frame rate: %d/%d\n", + i, + ff->format_ctx->streams[i]->avg_frame_rate.num, + ff->format_ctx->streams[i]->avg_frame_rate.den); + printf(" * stream %d start time: %lld\n", + i, + ff->format_ctx->streams[i]->start_time); + printf(" * stream %d duration : %lld\n", + i, + ff->format_ctx->streams[i]->duration); + printf(" * stream %d nb frames : %lld\n", + i, + ff->format_ctx->streams[i]->nb_frames); + + codec_par = ff->format_ctx->streams[i]->codecpar; + codec = avcodec_find_decoder(codec_par->codec_id); + + if (!codec) + { + printf("unsupported codec, continuing\n"); + continue; + } + + if (codec_par->codec_type == AVMEDIA_TYPE_VIDEO) + { + video = (FF_Video *)calloc(1, sizeof(FF_Video)); + if (!video) + { + printf("fail to allocate memory for video\n"); + continue; + } + + video->codec_par = codec_par; + video->codec = codec; + video->stream_index = i; + eina_array_push(ff->videos, video); + + if (ff->video_index == -1) + { + ff->video_index = 0; + } + } + else if (codec_par->codec_type == AVMEDIA_TYPE_AUDIO) + { + FF_Audio *audio; + + audio = (FF_Audio *)calloc(1, sizeof(FF_Audio)); + if (!audio) + { + printf("fail to allocate memory for audio\n"); + continue; + } + + audio->codec_par = codec_par; + audio->codec = codec; + audio->stream_index = i; + eina_array_push(ff->audios, audio); + + if (ff->audio_index == -1) + { + ff->audio_index = 0; + } + } + } + + if (ff->video_index == -1) + { + printf("WARNING : no video stream`n"); + } + + video = (FF_Video *)eina_array_data_get(ff->videos, ff->video_index); + ff->codec_ctx = avcodec_alloc_context3(video->codec); + if (!ff->codec_ctx) + { + goto free_eina_lists; + } + + if (avcodec_parameters_to_context(ff->codec_ctx, video->codec_par) < 0) + { + printf("WARNING : no video stream`n"); + goto free_codec_ctx; + } + + if (avcodec_open2(ff->codec_ctx, video->codec, NULL) < 0) + { + printf("failed to open codec through avcodec_open2\n"); + goto free_codec_ctx; + } + + return 1; + + free_codec_ctx: + avcodec_free_context(&ff->codec_ctx); + free_eina_lists: + _ff_free_lists(ff); + free_format_ctx: + avformat_free_context(ff->format_ctx); + ff->audio_index = -1; + ff->video_index = -1; + + return 0; +} + +void +ff_file_close(FFmpeg *ff) +{ + avcodec_free_context(&ff->codec_ctx); + _ff_free_lists(ff); + avformat_free_context(ff->format_ctx); + ff->audio_index = -1; + ff->video_index = -1; +} + +void +ff_size_get(FFmpeg *ff, int *width, int *height) +{ + FF_Video *v; + + if (width) *width = 0; + if (height) *height = 0; + if (ff->video_index < 0) + return; + + v = (FF_Video *)eina_array_data_get(ff->videos, ff->video_index); + + if (width) + { + *width = ff->format_ctx->streams[v->stream_index]->codecpar->width; + } + + if (height) + { + *height = ff->format_ctx->streams[v->stream_index]->codecpar->height; + } +} + +double +ff_len_get(FFmpeg *ff) +{ + /* + * get duration of the file, not of the video stream + * see https://stackoverflow.com/a/32538549 + */ + return ff->format_ctx->duration / (double)AV_TIME_BASE; +} + +int +_ff_fps_get(FFmpeg *ff, int *n, int *d) +{ + FF_Video *v; + + if (n) *n = 0; + if (d) *d = 0; + if (ff->video_index < 0) + return 0; + + v = (FF_Video *)eina_array_data_get(ff->videos, ff->video_index); + + if (n) + *n = ff->format_ctx->streams[v->stream_index]->avg_frame_rate.num; + + if (d) + *d = ff->format_ctx->streams[v->stream_index]->avg_frame_rate.den; + + return 1; +} + +int +ff_fps_num_get(FFmpeg *ff) +{ + int num; + + _ff_fps_get(ff, &num, NULL); + + return num; +} + +int +ff_fps_den_get(FFmpeg *ff) +{ + int den; + + _ff_fps_get(ff, NULL, &den); + + return den; +} + +double +ff_fps_get(FFmpeg *ff) +{ + int num, den; + + if (!_ff_fps_get(ff, &num, &den)) + return 0.0; + + return (double)num / (double)den; +} + +int +ff_video_handled(FFmpeg *ff) +{ + return ff->video_index >= 0; +} + +int +ff_audio_handled(FFmpeg *ff) +{ + return ff->audio_index >= 0; +} + +int +ff_format_get(FFmpeg *ff) +{ +} + +int +ff_video_channel_count(FFmpeg *ff) +{ + return eina_array_count_get(ff->videos); +} + +void +ff_video_channel_set(FFmpeg *ff, int channel) +{ + if ((channel >= 0) || (channel < ff_video_channel_count(ff))) + { + ff->video_index = channel; + } +} + +int +ff_video_channel_get(FFmpeg *ff) +{ + return ff->video_index; +} + +int +ff_audio_channel_count(FFmpeg *ff) +{ + return eina_array_count_get(ff->audios); +} + +void +ff_audio_channel_set(FFmpeg *ff, int channel) +{ + if ((channel >= 0) || (channel < ff_audio_channel_count(ff))) + { + ff->audio_index = channel; + } +} + +int +ff_audio_channel_get(FFmpeg *ff) +{ + return ff->audio_index; +} diff --git a/ffmpeg.h b/ffmpeg.h new file mode 100644 index 0000000..fdbb782 --- /dev/null +++ b/ffmpeg.h @@ -0,0 +1,71 @@ +#ifndef FFMPEG_H +#define FFMPEG_H + +typedef struct FF_Video FF_Video; +typedef struct FF_Audio FF_Audio; +typedef struct FFmpeg FFmpeg; + +struct FF_Video +{ + const AVCodecParameters *codec_par; + const AVCodec *codec; + int stream_index; +}; + +struct FF_Audio +{ + const AVCodecParameters *codec_par; + const AVCodec *codec; + int stream_index; +}; + +struct FFmpeg +{ + AVFormatContext *format_ctx; + AVCodecContext *codec_ctx; + Eina_Array *videos; + Eina_Array *audios; + int video_index; + int audio_index; +}; + + +FFmpeg *ff_new(void); + +void ff_free(FFmpeg *ff); + +int ff_file_open(FFmpeg *ff, const char *file); + +void ff_file_close(FFmpeg *ff); + +void ff_size_get(FFmpeg *ff, int *width, int *height); + +double ff_len_get(FFmpeg *ff); + +int _ff_fps_get(FFmpeg *ff, int *n, int *d); + +int ff_fps_num_get(FFmpeg *ff); + +int ff_fps_den_get(FFmpeg *ff); + +double ff_fps_get(FFmpeg *ff); + +int ff_video_handled(FFmpeg *ff); + +int ff_audio_handled(FFmpeg *ff); + +int ff_format_get(FFmpeg *ff); + +int ff_video_channel_count(FFmpeg *ff); + +void ff_video_channel_set(FFmpeg *ff, int channel); + +int ff_video_channel_get(FFmpeg *ff); + +int ff_audio_channel_count(FFmpeg *ff); + +void ff_audio_channel_set(FFmpeg *ff, int channel); + +int ff_audio_channel_get(FFmpeg *ff); + +#endif /* FFMPEG_H */ diff --git a/main.c b/main.c new file mode 100644 index 0000000..b75f5c2 --- /dev/null +++ b/main.c @@ -0,0 +1,299 @@ +/* gcc -g -Wall -Wextra -o ffmpeg main.c ffmpeg.c `pkg-config --cflags --libs ecore-evas libavcodec libavformat libavutil libswscale ecore-evas` */ + +#include + +#include +#include + +#include +#include +#include + +#include "ffmpeg.h" + +typedef struct +{ + Evas_Object *img; + FFmpeg *ff; + AVFrame *frame; + AVPacket *packet; + struct SwsContext *swscale_ctx; + int current_frame; +} FF_Ctx; + + +void _quit(Ecore_Evas *ee) +{ + ecore_main_loop_quit(); + (void)ee; +} + +void _resize(Ecore_Evas *ee) +{ + printf("resize\n"); + (void)ee; +} + +Eina_Bool +display_frame_cb(void *data) +{ + FF_Ctx *ff_ctx = data; + FF_Video *v; + + ff_ctx->current_frame++; + printf("NEW : %d\n", ff_ctx->current_frame); + + while (av_read_frame(ff_ctx->ff->format_ctx, ff_ctx->packet) >= 0) + { + v = eina_array_data_get(ff_ctx->ff->videos, ff_ctx->ff->video_index); + if (ff_ctx->packet->stream_index == v->stream_index) + { + int response; + + response = avcodec_send_packet(ff_ctx->ff->codec_ctx, ff_ctx->packet); + if (response < 0) + return ECORE_CALLBACK_RENEW; + while (response >= 0) + { + response = avcodec_receive_frame(ff_ctx->ff->codec_ctx, ff_ctx->frame); + if (response == AVERROR(EAGAIN) || response == AVERROR_EOF) + { + break; + } + else if (response < 0) { + return ECORE_CALLBACK_RENEW; + } + + if (response >= 0) + { + printf("fame %d: size %d x %d ptr %p %p %p stride: %d %d %d\n", + ff_ctx->ff->codec_ctx->frame_number, + ff_ctx->frame->width, + ff_ctx->frame->height, + ff_ctx->frame->data[0], + ff_ctx->frame->data[1], + ff_ctx->frame->data[2], + ff_ctx->frame->linesize[0], + ff_ctx->frame->linesize[1], + ff_ctx->frame->linesize[2]); + evas_object_image_size_set(ff_ctx->img, ff_ctx->frame->width, ff_ctx->frame->height); + evas_object_resize(ff_ctx->img, ff_ctx->frame->width, ff_ctx->frame->height); + if (ff_ctx->frame->format != AV_PIX_FMT_YUV420P) + { + printf("NOOOOOO !"); + } + else + { + evas_object_image_alpha_set(ff_ctx->img, 0); + evas_object_image_colorspace_set(ff_ctx->img, EVAS_COLORSPACE_YCBCR422P601_PL); + evas_object_image_size_set(ff_ctx->img, ff_ctx->frame->width, ff_ctx->frame->height); + evas_object_resize(ff_ctx->img, ff_ctx->frame->width, ff_ctx->frame->height); + unsigned char *iter_src; + unsigned char **data; + unsigned char **iter_dst; + int i; + + data = evas_object_image_data_get(ff_ctx->img, 1); + if (!data) + printf("arg NO NO\n"); + iter_src = ff_ctx->frame->data[0]; + iter_dst = data; + for (i = 0; i < ff_ctx->frame->height; i++) + { + printf(" i == %d\n", i); + *iter_dst = NULL; + *iter_dst = iter_src; + iter_dst++; + iter_src += ff_ctx->frame->linesize[0]; + printf(" **** %d\n", (int)(iter_src - ff_ctx->frame->data[0])); + } + + iter_src = ff_ctx->frame->data[1]; + for (i = 0; i < ff_ctx->frame->height / 2; i++) + { + *iter_dst = iter_src; + iter_dst++; + iter_src += ff_ctx->frame->linesize[1]; + } + + iter_src = ff_ctx->frame->data[2]; + for (i = 0; i < ff_ctx->frame->height / 2; i++) + { + *iter_dst = iter_src; + iter_dst++; + iter_src += ff_ctx->frame->linesize[2]; + } + /* unsigned char *out_data[8] = { data, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; */ + /* int out_line[8] = { ff_ctx->frame->width * 4, 0, 0, 0, 0, 0, 0, 0 }; */ + /* sws_scale(ff_ctx->swscale_ctx, */ + /* (const uint8_t * const *)ff_ctx->frame->data, */ + /* (const int *)ff_ctx->frame->linesize, */ + /* 0, ff_ctx->frame->height, */ + /* out_data, out_line); */ + //unsigned char data = evas_object_image_data_set(ff_ctx->img, ff_ctx->frame->data[0]); + evas_object_image_data_set(ff_ctx->img, data); + evas_object_image_data_update_add(ff_ctx->img, + 0, 0, + ff_ctx->frame->width, + ff_ctx->frame->height); + evas_object_image_pixels_dirty_set(ff_ctx->img, 0); + } + return ECORE_CALLBACK_RENEW; + } + } + } + } + +#if 0 + if (av_read_frame(ff_ctx->ff->format_ctx, ff_ctx->packet) >= 0) + { + v = eina_array_data_get(ff_ctx->ff->videos, ff_ctx->ff->video_index); + printf(" stream index : %d %d %d\n", + ff_ctx->packet->stream_index, + ff_ctx->ff->video_index, + v->stream_index); + if (ff_ctx->packet->stream_index == v->stream_index) + { + int response; + + printf("AVPacket->pts %lld\n", + ff_ctx->packet->pts); + + response = avcodec_send_packet(ff_ctx->ff->codec_ctx, ff_ctx->packet); + if (response < 0) + return ECORE_CALLBACK_RENEW; + while (response >= 0) + { + response = avcodec_receive_frame(ff_ctx->ff->codec_ctx, ff_ctx->frame); + if (response == AVERROR(EAGAIN) || response == AVERROR_EOF) + { + break; + } + else if (response < 0) { + return response; + } + + if (response >= 0) + { + printf("fame %d: size %d x %d\n", + ff_ctx->ff->codec_ctx->frame_number, + ff_ctx->frame->width, + ff_ctx->frame->height); + } + } + } + } +#endif + + return ECORE_CALLBACK_RENEW; +} + +void +print_info(FFmpeg *ff, int w, int h) +{ + int num, den; + printf(" * video size: %d %d\n", w, h); + printf(" * duration : %f s\n", ff_len_get(ff)); + _ff_fps_get(ff, &num, &den); + printf(" * video fps : %d %d %.2f %.2f\n", num, den, ff_fps_get(ff), 1.0 / ff_fps_get(ff)); +} + +int main(int argc, char *argv[]) +{ + FFmpeg *ff; + int res; + + Ecore_Animator *anim; + Ecore_Evas *ee; + Evas *evas; + Evas_Object *o; + int w; + int h; + + if (argc < 2) + { + printf("Usage: %s file\n", argv[0]); + return 1; + } + + ecore_evas_init(); + + ee = ecore_evas_new("software_gdi", 1, 1, 0, 0, NULL); + if (!ee) + { + ecore_evas_shutdown(); + return 1; + } + ecore_evas_callback_delete_request_set(ee, _quit); + ecore_evas_callback_resize_set(ee, _resize); + + evas = ecore_evas_get(ee); + + o = evas_object_image_filled_add(evas); + evas_object_move(o, 0, 0); + evas_object_show(o); + + ff = ff_new(); + if (!ff) + { + printf("can not create ff\n"); + return 1; + } + + res= ff_file_open(ff, argv[1]); + if (!res) + { + printf("can not open file%s\n", argv[1]); + return 1; + } + ff_size_get(ff, &w, &h); + print_info(ff, w, h); + + FF_Ctx ff_ctx; + + ff_ctx.current_frame = 0; + ff_ctx.img = o; + ff_ctx.ff = ff; + ff_ctx.frame = av_frame_alloc(); + if (!ff_ctx.frame) + { + printf("av_frame_alloc() failed\n"); + return 0; + } + + ff_ctx.packet = av_packet_alloc(); + if (!ff_ctx.packet) + { + printf("av_packet_alloc() failed\n"); + return 0; + } + + ff_ctx.swscale_ctx = sws_getContext(w, h, AV_PIX_FMT_YUV420P, + w, h, AV_PIX_FMT_RGB32, + SWS_BILINEAR, NULL, NULL, NULL); + if (!ff_ctx.swscale_ctx) + { + printf("sws_alloc_context() failed\n"); + return 0; + } + + ecore_animator_frametime_set(1.0 / ff_fps_get(ff)); + anim = ecore_animator_add(display_frame_cb, &ff_ctx); + if (!anim) + { + printf("no anim\n"); + return 0; + } + + ecore_evas_resize(ee, w, h); + ecore_evas_show(ee); + + ecore_main_loop_begin(); + + ff_file_close(ff); + ff_free(ff); + + ecore_evas_shutdown(); + + return 0; +}