From c29fed1ac81982e845aae43f06ff23f3b5c08008 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 21 Apr 2017 12:36:46 +0200 Subject: [PATCH] video-play: add example video capture + playback --- pinos/examples/meson.build | 6 + pinos/examples/video-play.c | 416 ++++++++++++++++++++++++++++++++++++ 2 files changed, 422 insertions(+) create mode 100644 pinos/examples/video-play.c diff --git a/pinos/examples/meson.build b/pinos/examples/meson.build index a8f9b6351..9d0142463 100644 --- a/pinos/examples/meson.build +++ b/pinos/examples/meson.build @@ -3,3 +3,9 @@ executable('video-src', install: false, dependencies : [pinos_dep], ) + +executable('video-play', + 'video-play.c', + install: false, + dependencies : [pinos_dep,sdl_dep], +) diff --git a/pinos/examples/video-play.c b/pinos/examples/video-play.c new file mode 100644 index 000000000..ac2e09522 --- /dev/null +++ b/pinos/examples/video-play.c @@ -0,0 +1,416 @@ +/* Pinos + * Copyright (C) 2017 Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +typedef struct { + uint32_t format; + uint32_t props; + SpaTypeMediaType media_type; + SpaTypeMediaSubtype media_subtype; + SpaTypeFormatVideo format_video; + SpaTypeVideoFormat video_format; +} Type; + +static inline void +init_type (Type *type, SpaTypeMap *map) +{ + type->format = spa_type_map_get_id (map, SPA_TYPE__Format); + type->props = spa_type_map_get_id (map, SPA_TYPE__Props); + spa_type_media_type_map (map, &type->media_type); + spa_type_media_subtype_map (map, &type->media_subtype); + spa_type_format_video_map (map, &type->format_video); + spa_type_video_format_map (map, &type->video_format); +} + +#define WIDTH 640 +#define HEIGHT 480 +#define BPP 3 + +typedef struct { + Type type; + + const char *path; + + SDL_Renderer *renderer; + SDL_Window *window; + SDL_Texture *texture; + + bool running; + PinosLoop *loop; + SpaSource *timer; + + PinosContext *context; + PinosListener on_state_changed; + + PinosStream *stream; + PinosListener on_stream_state_changed; + PinosListener on_stream_format_changed; + PinosListener on_stream_new_buffer; + + SpaVideoInfoRaw format; + int32_t stride; + + uint8_t params_buffer[1024]; + int counter; +} Data; + +static void +handle_events (Data *data) +{ + SDL_Event event; + while (SDL_PollEvent (&event)) { + switch (event.type) { + case SDL_QUIT: + exit (0); + break; + } + } +} + +static void +on_stream_new_buffer (PinosListener *listener, + PinosStream *stream, + uint32_t id) +{ + Data *data = SPA_CONTAINER_OF (listener, Data, on_stream_new_buffer); + SpaBuffer *buf; + uint8_t *map; + void *sdata, *ddata; + int sstride, dstride, ostride; + int i; + uint8_t *src, *dst; + + buf = pinos_stream_peek_buffer (data->stream, id); + + if (buf->datas[0].type == SPA_DATA_TYPE_MEMFD) { + map = mmap (NULL, buf->datas[0].maxsize + buf->datas[0].mapoffset, PROT_READ, + MAP_PRIVATE, buf->datas[0].fd, 0); + sdata = SPA_MEMBER (map, buf->datas[0].mapoffset, uint8_t); + } + else if (buf->datas[0].type == SPA_DATA_TYPE_MEMPTR) { + map = NULL; + sdata = buf->datas[0].data; + } else + return; + + if (SDL_LockTexture (data->texture, NULL, &ddata, &dstride) < 0) { + fprintf (stderr, "Couldn't lock texture: %s\n", SDL_GetError()); + return; + } + sstride = buf->datas[0].chunk->stride; + ostride = SPA_MIN (sstride, dstride); + + src = sdata; + dst = ddata; + for (i = 0; i < data->format.size.height; i++) { + memcpy (dst, src, ostride); + src += sstride; + dst += dstride; + } + SDL_UnlockTexture(data->texture); + + SDL_RenderClear (data->renderer); + SDL_RenderCopy (data->renderer, data->texture, NULL, NULL); + SDL_RenderPresent (data->renderer); + + if (map) + munmap (map, buf->datas[0].maxsize); + + pinos_stream_recycle_buffer (data->stream, id); + + handle_events (data); +} + +static void +on_stream_state_changed (PinosListener *listener, + PinosStream *stream) +{ + printf ("stream state: \"%s\"\n", pinos_stream_state_as_string (stream->state)); +} + +static struct { + Uint32 format; + uint32_t id; +} video_formats[] = { + { SDL_PIXELFORMAT_UNKNOWN, offsetof (SpaTypeVideoFormat, UNKNOWN), }, + { SDL_PIXELFORMAT_INDEX1LSB, offsetof (SpaTypeVideoFormat, UNKNOWN), }, + { SDL_PIXELFORMAT_UNKNOWN, offsetof (SpaTypeVideoFormat, UNKNOWN), }, + { SDL_PIXELFORMAT_INDEX1LSB, offsetof (SpaTypeVideoFormat, UNKNOWN), }, + { SDL_PIXELFORMAT_INDEX1MSB, offsetof (SpaTypeVideoFormat, UNKNOWN), }, + { SDL_PIXELFORMAT_INDEX4LSB, offsetof (SpaTypeVideoFormat, UNKNOWN), }, + { SDL_PIXELFORMAT_INDEX4MSB, offsetof (SpaTypeVideoFormat, UNKNOWN), }, + { SDL_PIXELFORMAT_INDEX8, offsetof (SpaTypeVideoFormat, UNKNOWN), }, + { SDL_PIXELFORMAT_RGB332, offsetof (SpaTypeVideoFormat, UNKNOWN), }, + { SDL_PIXELFORMAT_RGB444, offsetof (SpaTypeVideoFormat, UNKNOWN), }, + { SDL_PIXELFORMAT_RGB555, offsetof (SpaTypeVideoFormat, UNKNOWN), }, + { SDL_PIXELFORMAT_BGR555, offsetof (SpaTypeVideoFormat, UNKNOWN), }, + { SDL_PIXELFORMAT_ARGB4444, offsetof (SpaTypeVideoFormat, UNKNOWN), }, + { SDL_PIXELFORMAT_RGBA4444, offsetof (SpaTypeVideoFormat, UNKNOWN), }, + { SDL_PIXELFORMAT_ABGR4444, offsetof (SpaTypeVideoFormat, UNKNOWN), }, + { SDL_PIXELFORMAT_BGRA4444, offsetof (SpaTypeVideoFormat, UNKNOWN), }, + { SDL_PIXELFORMAT_ARGB1555, offsetof (SpaTypeVideoFormat, UNKNOWN), }, + { SDL_PIXELFORMAT_RGBA5551, offsetof (SpaTypeVideoFormat, UNKNOWN), }, + { SDL_PIXELFORMAT_ABGR1555, offsetof (SpaTypeVideoFormat, UNKNOWN), }, + { SDL_PIXELFORMAT_BGRA5551, offsetof (SpaTypeVideoFormat, UNKNOWN), }, + { SDL_PIXELFORMAT_RGB565, offsetof (SpaTypeVideoFormat, UNKNOWN), }, + { SDL_PIXELFORMAT_BGR565, offsetof (SpaTypeVideoFormat, UNKNOWN), }, + { SDL_PIXELFORMAT_RGB24, offsetof (SpaTypeVideoFormat, RGB), }, + { SDL_PIXELFORMAT_RGB888, offsetof (SpaTypeVideoFormat, RGB), }, + { SDL_PIXELFORMAT_RGBX8888, offsetof (SpaTypeVideoFormat, RGBx), }, + { SDL_PIXELFORMAT_BGR24, offsetof (SpaTypeVideoFormat, BGR), }, + { SDL_PIXELFORMAT_BGR888, offsetof (SpaTypeVideoFormat, BGR), }, + { SDL_PIXELFORMAT_BGRX8888, offsetof (SpaTypeVideoFormat, BGRx), }, + { SDL_PIXELFORMAT_ARGB2101010, offsetof (SpaTypeVideoFormat, UNKNOWN), }, + { SDL_PIXELFORMAT_RGBA32, offsetof (SpaTypeVideoFormat, RGBA), }, + { SDL_PIXELFORMAT_ARGB32, offsetof (SpaTypeVideoFormat, ARGB), }, + { SDL_PIXELFORMAT_BGRA32, offsetof (SpaTypeVideoFormat, BGRA), }, + { SDL_PIXELFORMAT_ABGR32, offsetof (SpaTypeVideoFormat, ABGR), }, + { SDL_PIXELFORMAT_YV12, offsetof (SpaTypeVideoFormat, YV12), }, + { SDL_PIXELFORMAT_IYUV, offsetof (SpaTypeVideoFormat, I420), }, + { SDL_PIXELFORMAT_YUY2, offsetof (SpaTypeVideoFormat, YUY2), }, + { SDL_PIXELFORMAT_UYVY, offsetof (SpaTypeVideoFormat, UYVY), }, + { SDL_PIXELFORMAT_YVYU, offsetof (SpaTypeVideoFormat, YVYU), }, + { SDL_PIXELFORMAT_NV12, offsetof (SpaTypeVideoFormat, NV12), }, + { SDL_PIXELFORMAT_NV21, offsetof (SpaTypeVideoFormat, NV21), } +}; + +static uint32_t +sdl_format_to_id (Data *data, Uint32 format) +{ + int i; + + for (i = 0; i < SPA_N_ELEMENTS (video_formats); i++) { + if (video_formats[i].format == format) + return *SPA_MEMBER (&data->type.video_format, video_formats[i].id, uint32_t); + } + return data->type.video_format.UNKNOWN; +} + +static Uint32 +id_to_sdl_format (Data *data, uint32_t id) +{ + int i; + + for (i = 0; i < SPA_N_ELEMENTS (video_formats); i++) { + if (*SPA_MEMBER (&data->type.video_format, video_formats[i].id, uint32_t) == id) + return video_formats[i].format; + } + return SDL_PIXELFORMAT_UNKNOWN; +} + +#define PROP(f,key,type,...) \ + SPA_POD_PROP (f,key,0,type,1,__VA_ARGS__) +#define PROP_U_MM(f,key,type,...) \ + SPA_POD_PROP (f,key,SPA_POD_PROP_FLAG_UNSET | \ + SPA_POD_PROP_RANGE_MIN_MAX,type,3,__VA_ARGS__) + +static void +on_stream_format_changed (PinosListener *listener, + PinosStream *stream, + SpaFormat *format) +{ + Data *data = SPA_CONTAINER_OF (listener, Data, on_stream_format_changed); + PinosContext *ctx = stream->context; + SpaPODBuilder b = { NULL }; + SpaPODFrame f[2]; + SpaAllocParam *params[2]; + + if (format) { + Uint32 sdl_format; + + spa_debug_format (format, data->context->type.map); + + spa_format_video_raw_parse (format, &data->format, &data->type.format_video); + + sdl_format = id_to_sdl_format (data, data->format.format); + if (sdl_format == SDL_PIXELFORMAT_UNKNOWN) { + pinos_stream_finish_format (stream, SPA_RESULT_ERROR, NULL, 0); + return; + } + + data->texture = SDL_CreateTexture (data->renderer, + sdl_format, + SDL_TEXTUREACCESS_STREAMING, + data->format.size.width, + data->format.size.height); + data->stride = data->format.size.width * 4; + + spa_pod_builder_init (&b, data->params_buffer, sizeof (data->params_buffer)); + spa_pod_builder_object (&b, &f[0], 0, ctx->type.alloc_param_buffers.Buffers, + PROP (&f[1], ctx->type.alloc_param_buffers.size, SPA_POD_TYPE_INT, + data->stride * data->format.size.height), + PROP (&f[1], ctx->type.alloc_param_buffers.stride, SPA_POD_TYPE_INT, data->stride), + PROP_U_MM (&f[1], ctx->type.alloc_param_buffers.buffers, SPA_POD_TYPE_INT, 32, 2, 32), + PROP (&f[1], ctx->type.alloc_param_buffers.align, SPA_POD_TYPE_INT, 16)); + params[0] = SPA_POD_BUILDER_DEREF (&b, f[0].ref, SpaAllocParam); + + spa_pod_builder_object (&b, &f[0], 0, ctx->type.alloc_param_meta_enable.MetaEnable, + PROP (&f[1], ctx->type.alloc_param_meta_enable.type, SPA_POD_TYPE_INT, SPA_META_TYPE_HEADER)); + params[1] = SPA_POD_BUILDER_DEREF (&b, f[0].ref, SpaAllocParam); + + pinos_stream_finish_format (stream, SPA_RESULT_OK, params, 2); + } + else { + pinos_stream_finish_format (stream, SPA_RESULT_OK, NULL, 0); + } +} +static void +on_state_changed (PinosListener *listener, + PinosContext *context) +{ + Data *data = SPA_CONTAINER_OF (listener, Data, on_state_changed); + + switch (context->state) { + case PINOS_CONTEXT_STATE_ERROR: + printf ("context error: %s\n", context->error); + data->running = false; + break; + + case PINOS_CONTEXT_STATE_CONNECTED: + { + SpaFormat *formats[1]; + uint8_t buffer[1024]; + SpaPODBuilder b = SPA_POD_BUILDER_INIT (buffer, sizeof (buffer)); + SpaPODFrame f[2]; + SDL_RendererInfo info; + int i, c; + + printf ("context state: \"%s\"\n", pinos_context_state_as_string (context->state)); + + data->stream = pinos_stream_new (context, "video-play", NULL); + + SDL_GetRendererInfo(data->renderer, &info); + + spa_pod_builder_push_format (&b, &f[0], data->type.format, + data->type.media_type.video, data->type.media_subtype.raw); + + spa_pod_builder_push_prop (&b, &f[1], data->type.format_video.format, + SPA_POD_PROP_FLAG_UNSET | + SPA_POD_PROP_RANGE_ENUM); + for (i = 0, c = 0; i < info.num_texture_formats; i++) { + uint32_t id = sdl_format_to_id (data, info.texture_formats[i]); + if (id == 0) + continue; + if (c++ == 0) + spa_pod_builder_id (&b, id); + spa_pod_builder_id (&b, id); + } + for (i = 0; i < SPA_N_ELEMENTS (video_formats); i++) { + uint32_t id = *SPA_MEMBER (&data->type.video_format, video_formats[i].id, uint32_t); + if (id != data->type.video_format.UNKNOWN) + spa_pod_builder_id (&b, id); + } + spa_pod_builder_pop (&b, &f[1]); + spa_pod_builder_add (&b, + PROP_U_MM (&f[1], data->type.format_video.size, + SPA_POD_TYPE_RECTANGLE, WIDTH, HEIGHT, + 1, 1, + info.max_texture_width, info.max_texture_height), + PROP_U_MM (&f[1], data->type.format_video.framerate, + SPA_POD_TYPE_FRACTION, 25, 1, + 0, 1, + 30, 1), + 0); + + spa_pod_builder_pop (&b, &f[0]); + formats[0] = SPA_POD_BUILDER_DEREF (&b, f[0].ref, SpaFormat); + + printf ("supported formats:\n"); + spa_debug_format (formats[0], data->context->type.map); + + pinos_signal_add (&data->stream->state_changed, + &data->on_stream_state_changed, + on_stream_state_changed); + pinos_signal_add (&data->stream->format_changed, + &data->on_stream_format_changed, + on_stream_format_changed); + pinos_signal_add (&data->stream->new_buffer, + &data->on_stream_new_buffer, + on_stream_new_buffer); + + pinos_stream_connect (data->stream, + PINOS_DIRECTION_INPUT, + PINOS_STREAM_MODE_BUFFER, + data->path, + PINOS_STREAM_FLAG_AUTOCONNECT, + 1, + formats); + break; + } + default: + printf ("context state: \"%s\"\n", pinos_context_state_as_string (context->state)); + break; + } +} + +int +main (int argc, char *argv[]) +{ + Data data = { 0, }; + + pinos_init (&argc, &argv); + + data.loop = pinos_loop_new (); + data.running = true; + data.context = pinos_context_new (data.loop, "video-play", NULL); + data.path = argc > 1 ? argv[1] : NULL; + + init_type (&data.type, data.context->type.map); + + if (SDL_Init (SDL_INIT_VIDEO) < 0) { + printf ("can't initialize SDL: %s\n", SDL_GetError ()); + return -1; + } + + if (SDL_CreateWindowAndRenderer (WIDTH, HEIGHT, SDL_WINDOW_RESIZABLE, &data.window, &data.renderer)) { + printf ("can't create window: %s\n", SDL_GetError ()); + return -1; + } + + pinos_signal_add (&data.context->state_changed, + &data.on_state_changed, + on_state_changed); + + pinos_context_connect (data.context); + + pinos_loop_enter (data.loop); + while (data.running) { + pinos_loop_iterate (data.loop, -1); + } + pinos_loop_leave (data.loop); + + pinos_context_destroy (data.context); + pinos_loop_destroy (data.loop); + + return 0; +}