Permalink
Switch branches/tags
Nothing to show
Find file
Fetching contributors…
Cannot retrieve contributors at this time
400 lines (337 sloc) 11 KB
/* See Copyright Notice in LICENSE.txt */
/*
* Includes from code by Michael Meeuwisse
* https://docs.google.com/leaf?id=0B_dz2NwhjXB-NDQ0NWNjOWEtMzJiNy00ZjcwLWJjMjYtZTU2YmQzMWMzYmU0
*
* License:
*
* (C) Copyright 2010 Michael Meeuwisse
*
* Adapted from avcodec_sample.0.5.0.c, license unknown
*
* ffmpeg_test is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* ffmpeg_test 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with ffmpeg_test. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <GL/glew.h>
#include <GL/gl.h>
#include <lauxlib.h>
#include <lualib.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include "misc.h"
#include "shader.h"
typedef struct {
AVFormatContext *format_context;
AVCodecContext *codec_context;
AVCodec *codec;
AVFrame *raw_frame;
AVFrame *scaled_frame;
uint8_t *buffer;
struct SwsContext *scaler;
int stream_idx, format;
int width, height;
int buffer_width, buffer_height;
double par;
GLuint tex;
double fps;
int finished;
} video_t;
LUA_TYPE_DECL(video)
/* Helper functions */
static void video_free(video_t *video) {
if (video->scaler)
sws_freeContext(video->scaler);
if (video->raw_frame)
av_free(video->raw_frame);
if (video->scaled_frame)
av_free(video->scaled_frame);
if (video->codec_context)
avcodec_close(video->codec_context);
if (video->format_context)
avformat_close_input(&video->format_context);
av_free(video->buffer);
}
static int video_open(video_t *video, const char *filename) {
video->finished = 0;
video->format = PIX_FMT_RGB24;
if (avformat_open_input(&video->format_context, filename, NULL, NULL) ||
avformat_find_stream_info(video->format_context, NULL) < 0) {
fprintf(stderr, ERROR("cannot open video stream %s\n"), filename);
goto failed;
}
video->stream_idx = -1;
for (int i = 0; i < video->format_context->nb_streams; i++) {
if (video->format_context->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
video->stream_idx = i;
break;
}
}
if (video->stream_idx == -1) {
fprintf(stderr, ERROR("cannot find video stream\n"));
goto failed;
}
AVStream *stream = video->format_context->streams[video->stream_idx];
video->codec_context = stream->codec;
video->codec = avcodec_find_decoder(video->codec_context->codec_id);
/* Save Width/Height */
video->width = video->codec_context->width;
video->height = video->codec_context->height;
if (!video->codec || avcodec_open2(video->codec_context, video->codec, NULL) < 0) {
fprintf(stderr, ERROR("cannot open codec\n"));
goto failed;
}
video->buffer_width = video->codec_context->width;
video->buffer_height = video->codec_context->height;
fprintf(stderr, INFO("pixel aspect ratio: %d/%d, size: %dx%d buffer size: %dx%d\n"),
video->codec_context->sample_aspect_ratio.num,
video->codec_context->sample_aspect_ratio.den,
video->width,
video->height,
video->buffer_width,
video->buffer_height
);
video->par = (float)video->codec_context->sample_aspect_ratio.num / video->codec_context->sample_aspect_ratio.den;
if (video->par == 0)
video->par = 1;
/* Frame rate fix for some codecs */
if (video->codec_context->time_base.num > 1000 && video->codec_context->time_base.den == 1)
video->codec_context->time_base.den = 1000;
/* Get FPS */
// http://libav-users.943685.n4.nabble.com/Retrieving-Frames-Per-Second-FPS-td946533.html
if ((stream->time_base.den != stream->avg_frame_rate.num) ||
(stream->time_base.num != stream->avg_frame_rate.den)) {
video->fps = 1.0 / stream->avg_frame_rate.den * stream->avg_frame_rate.num;
} else {
video->fps = 1.0 / stream->time_base.num * stream->time_base.den;
}
fprintf(stderr, INFO("fps: %lf\n"), video->fps);
/* Get framebuffers */
video->raw_frame = avcodec_alloc_frame();
video->scaled_frame = avcodec_alloc_frame();
if (!video->raw_frame || !video->scaled_frame) {
fprintf(stderr, ERROR("cannot preallocate frames\n"));
goto failed;
}
/* Create data buffer */
video->buffer = av_malloc(avpicture_get_size(
video->format,
video->buffer_width,
video->buffer_height
));
/* Init buffers */
avpicture_fill(
(AVPicture *) video->scaled_frame,
video->buffer,
video->format,
video->buffer_width,
video->buffer_height
);
/* Init scale & convert */
video->scaler = sws_getContext(
video->buffer_width,
video->buffer_height,
video->codec_context->pix_fmt,
video->buffer_width,
video->buffer_height,
video->format,
SWS_BICUBIC,
NULL,
NULL,
NULL
);
if (!video->scaler) {
fprintf(stderr, ERROR("scale context init failed\n"));
goto failed;
}
/* Give some info on stderr about the file & stream */
av_dump_format(video->format_context, 0, filename, 0);
return 1;
failed:
video_free(video);
return 0;
}
static int video_next_frame(video_t *video) {
AVPacket packet;
av_init_packet(&packet);
again:
/* Can we read a frame? */
if (av_read_frame(video->format_context, &packet)) {
fprintf(stderr, "no next frame\n");
video->finished = 1;
av_free_packet(&packet);
return 0;
}
/* Is it what we're trying to parse? */
if (packet.stream_index != video->stream_idx) {
// fprintf(stderr, "not video\n");
av_free_packet(&packet);
goto again;
}
/* Decode it! */
int complete_frame = 0;
avcodec_decode_video2(video->codec_context, video->raw_frame, &complete_frame, &packet);
/* Success? If not, drop packet. */
if (!complete_frame) {
fprintf(stderr, ERROR("incomplete video packet\n"));
av_free_packet(&packet);
goto again;
}
/* Flip vertically
* XXX: This feels wrong. What's the right way to do this?
*/
int heights[] = {
video->buffer_height - 1,
video->buffer_height / 2 - 1,
video->buffer_height / 2 - 1,
0,
};
for (int i = 0; i < 4; i++) {
video->raw_frame->data[i] += video->raw_frame->linesize[i] * heights[i];
video->raw_frame->linesize[i] = -video->raw_frame->linesize[i];
// fprintf(stderr, "%d -> %d\n", video->raw_frame->linesize[i], video->scaled_frame->linesize[i]);
}
sws_scale(
video->scaler,
(const uint8_t* const *)video->raw_frame->data,
video->raw_frame->linesize,
0,
video->buffer_height,
video->scaled_frame->data,
video->scaled_frame->linesize
);
av_free_packet(&packet);
return 1;
}
/* Instance methods */
static int video_size(lua_State *L) {
video_t *video = checked_video(L, 1);
lua_pushnumber(L, video->width);
lua_pushnumber(L, video->height / video->par);
return 2;
}
static int video_fps(lua_State *L) {
video_t *video = checked_video(L, 1);
lua_pushnumber(L, video->fps);
return 1;
}
static int video_next(lua_State *L) {
video_t *video = checked_video(L, 1);
if (!video_next_frame(video)) {
lua_pushboolean(L, 0);
return 1;
}
glBindTexture(GL_TEXTURE_2D, video->tex);
glPushClientAttrib(GL_CLIENT_PIXEL_STORE_BIT);
glPixelStorei(GL_UNPACK_SWAP_BYTES, GL_TRUE);
glPixelStorei(GL_UNPACK_LSB_FIRST, GL_TRUE);
glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
glPixelStorei(GL_UNPACK_SKIP_ROWS, video->buffer_height - video->height);
glPixelStorei(GL_UNPACK_ROW_LENGTH, video->buffer_width);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glTexSubImage2D(
GL_TEXTURE_2D,
0,
0,
0,
video->width,
video->height,
GL_RGB,
GL_UNSIGNED_BYTE,
video->buffer
);
glGenerateMipmap(GL_TEXTURE_2D);
glPopClientAttrib();
lua_pushboolean(L, 1);
return 1;
}
static int video_state(lua_State *L) {
video_t *video = checked_video(L, 1);
lua_pushstring(L, video->finished ? "finished" : "loaded");
lua_pushnumber(L, video->width);
lua_pushnumber(L, video->height / video->par);
lua_pushnumber(L, video->fps);
return 4;
}
static int video_draw(lua_State *L) {
video_t *video = checked_video(L, 1);
GLfloat x1 = luaL_checknumber(L, 2);
GLfloat y1 = luaL_checknumber(L, 3);
GLfloat x2 = luaL_checknumber(L, 4);
GLfloat y2 = luaL_checknumber(L, 5);
GLfloat alpha = luaL_optnumber(L, 6, 1.0);
glBindTexture(GL_TEXTURE_2D, video->tex);
shader_set_gl_color(1.0, 1.0, 1.0, alpha);
glBegin(GL_QUADS);
glTexCoord2f(0.0, 1.0); glVertex3f(x1, y1, 0);
glTexCoord2f(1.0, 1.0); glVertex3f(x2, y1, 0);
glTexCoord2f(1.0, 0.0); glVertex3f(x2, y2, 0);
glTexCoord2f(0.0, 0.0); glVertex3f(x1, y2, 0);
glEnd();
return 0;
}
static int video_texid(lua_State *L) {
video_t *video = checked_video(L, 1);
lua_pushnumber(L, video->tex);
return 1;
}
static int video_dispose(lua_State *L) {
return 0;
}
static const luaL_reg video_methods[] = {
{"state", video_state},
{"draw", video_draw},
{"next", video_next},
{"size", video_size},
{"fps", video_fps},
{"texid", video_texid},
{"dispose", video_dispose},
{0,0}
};
/* Lifecycle */
int video_load(lua_State *L, const char *path, const char *name) {
video_t video;
memset(&video, 0, sizeof(video_t));
if (!video_open(&video, path))
return luaL_error(L, "cannot open video %s", path);
glGenTextures(1, &video.tex);
glBindTexture(GL_TEXTURE_2D, video.tex);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
glTexImage2D(
GL_TEXTURE_2D,
0,
GL_RGB,
video.width,
video.height,
0,
GL_RGB,
GL_UNSIGNED_BYTE,
NULL
);
*push_video(L) = video;
return 1;
}
static int video_gc(lua_State *L) {
video_t *video = to_video(L, 1);
fprintf(stderr, INFO("gc'ing video: tex id: %d\n"), video->tex);
glDeleteTextures(1, &video->tex);
video_free(video);
return 0;
}
LUA_TYPE_IMPL(video)