TiVo® HME Tech Note #001: Video Backgrounds
© 2004-2005 TiVo Inc. |
public class VideoBackgroundDemo extends Application
{
/**
* Create the appplication.
*/
protected void init(Context context)
{
//
// Set the mpeg file as the resource of the root view. You can display
// a video background in any view, but the coordinate space defined by
// the view is ignored, the mpeg will be displayed at 0,0 in global screen
// coordinates.
//
root.setResource("myloop.mpg");
//
// Display some text above the video
//
View fg = new View(root, 0, 0, root.width, root.height);
fg.setResource(createText("default-20-bold.font",
Color.black, "The picture is a movie!"));
}
}
|
This release of HME supports single frame mpegs as video backgrounds. We have provided the source code to a C program "mkloop.c" that will convert a JPEG into an MPEG. mkloop requires ffmpeg, an open source mpeg encoder.
% make % mkloop foo.jpg foo.mpg
| mkloop.c |
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include "avformat.h"
#include "avcodec.h"
#define STREAM_NB_FRAMES 12
#define STREAM_GOP STREAM_NB_FRAMES
AVPicture input_picture;
enum PixelFormat img_fmt;
int img_width;
int img_height;
AVFrame *picture;
uint8_t *video_outbuf;
int frame_count, video_outbuf_size;
int img_alloc_cb(void *opaque, AVImageInfo *info)
{
unsigned char *data = malloc(info->width * info->height * 4);
img_width = info->width;
img_height = info->height;
img_fmt = info->pix_fmt;
avpicture_fill(&info->pict, data, info->pix_fmt, (info->width+15)&(~15), (info->height+15)&(~15));
memcpy(&input_picture, &info->pict, sizeof(AVPicture));
return 0;
}
AVStream *add_video_stream(AVFormatContext *oc, int codec_id)
{
AVCodecContext *c;
AVStream *st;
int aspect_ratio_numerator;
st = av_new_stream(oc, 0);
if (!st) {
fprintf(stderr, "Could not alloc stream\n");
exit(1);
}
c = &st->codec;
c->codec_id = codec_id;
c->codec_type = CODEC_TYPE_VIDEO;
c->bit_rate = 400000;
c->width = img_width;
c->height = img_height;
c->frame_rate = 30000;
c->frame_rate_base = 1001;
c->max_b_frames = 0;
c->gop_size = STREAM_GOP;
c->flags |= CODEC_FLAG_CLOSED_GOP;
c->scenechange_threshold = 1000000000;
aspect_ratio_numerator = ((img_height / (float) img_width) * 1.33333) * 1000;
c->sample_aspect_ratio.num = aspect_ratio_numerator;
c->sample_aspect_ratio.den = 1000;
return st;
}
AVFrame *alloc_picture(int pix_fmt, int width, int height)
{
AVFrame *picture;
uint8_t *picture_buf;
int size;
picture = avcodec_alloc_frame();
if (!picture)
return NULL;
size = avpicture_get_size(pix_fmt, width, height);
picture_buf = malloc(size);
if (!picture_buf) {
av_free(picture);
return NULL;
}
avpicture_fill((AVPicture *)picture, picture_buf,
pix_fmt, width, height);
return picture;
}
void open_video(AVFormatContext *oc, AVStream *st)
{
AVCodec *codec;
AVCodecContext *c;
c = &st->codec;
// find the video encoder
codec = avcodec_find_encoder(c->codec_id);
if (!codec) {
fprintf(stderr, "codec not found\n");
exit(1);
}
// open the codec
if (avcodec_open(c, codec) < 0) {
fprintf(stderr, "could not open codec\n");
exit(1);
}
video_outbuf = NULL;
if (!(oc->oformat->flags & AVFMT_RAWPICTURE)) {
video_outbuf_size = 200000;
video_outbuf = malloc(video_outbuf_size);
}
// allocate the encoded raw picture
picture = alloc_picture(c->pix_fmt, c->width, c->height);
if (!picture) {
fprintf(stderr, "Could not allocate picture\n");
exit(1);
}
}
void write_video_frame(AVFormatContext *oc, AVStream *st)
{
int out_size, ret;
AVCodecContext *c;
AVFrame *picture_ptr;
c = &st->codec;
if (frame_count >= STREAM_NB_FRAMES) {
/* no more frame to compress. The codec has a latency of a few
frames if using B frames, so we get the last frames by
passing a NULL picture */
picture_ptr = NULL;
} else {
img_convert((AVPicture *)picture, c->pix_fmt,
(AVPicture *)&input_picture, img_fmt,
c->width, c->height);
picture_ptr = picture;
if (frame_count == 0) {
picture->pts = 1000000;
}
}
/* encode the image */
out_size = avcodec_encode_video(c, video_outbuf, video_outbuf_size, picture_ptr);
/* if zero size, it means the image was buffered */
if (out_size != 0) {
AVPacket pkt;
av_init_packet(&pkt);
pkt.pts= c->coded_frame->pts;
pkt.dts= c->coded_frame->pts;
if(c->coded_frame->key_frame)
pkt.flags |= PKT_FLAG_KEY;
pkt.stream_index= st->index;
pkt.data= video_outbuf;
pkt.size= out_size;
/* write the compressed frame in the media file */
ret = av_write_frame(oc, &pkt);
} else {
ret = 0;
}
if (ret != 0) {
fprintf(stderr, "Error while writing video frame\n");
exit(1);
}
frame_count++;
}
void write_seq_end(AVFormatContext *oc, AVStream *st)
{
AVPacket pkt;
AVCodecContext *c;
c = &st->codec;
av_init_packet(&pkt);
pkt.pts= c->coded_frame->pts;
pkt.dts= c->coded_frame->pts;
pkt.stream_index= st->index;
pkt.data= video_outbuf;
video_outbuf[0] = 0x00;
video_outbuf[1] = 0x00;
video_outbuf[2] = 0x01;
video_outbuf[3] = 0xb7;
pkt.size= 4;
/* write the compressed frame in the media file */
av_write_frame(oc, &pkt);
}
void close_video(AVFormatContext *oc, AVStream *st)
{
avcodec_close(&st->codec);
av_free(picture->data[0]);
av_free(picture);
}
//
// convert image to video
//
int main(int argc, char **argv)
{
const char *ifilename;
const char *ofilename;
AVOutputFormat *fmt;
AVFormatContext *oc;
AVStream *video_st;
ByteIOContext bio;
int i;
// initialize libavcodec
av_register_all();
if (argc != 3) {
printf("usage: %s <input>.jpg <output>.mpg\n", argv[0]);
exit(1);
}
ifilename = argv[1];
ofilename = argv[2];
// read the image
if (url_fopen(&bio, ifilename, URL_RDONLY) < 0) {
fprintf(stderr, "failed to open image: %s\n", ifilename);
exit(1);
}
if (av_read_image(&bio, ifilename, NULL, img_alloc_cb, NULL) < 0) {
fprintf(stderr, "failed to read image: %s\n", ifilename);
exit(1);
}
url_fclose(&bio);
printf("input image %dx%d\n", img_width, img_height);
// get MPEG format
fmt = guess_format("vob", NULL, NULL);
// allocate the output media context
oc = av_alloc_format_context();
oc->oformat = fmt;
snprintf(oc->filename, sizeof(oc->filename), "%s", ofilename);
video_st = NULL;
if (fmt->video_codec != CODEC_ID_NONE) {
video_st = add_video_stream(oc, fmt->video_codec);
}
//video_st->quality = 1.0;
if (av_set_parameters(oc, NULL) < 0) {
fprintf(stderr, "invalid output format parameters\n");
exit(1);
}
dump_format(oc, 0, ofilename, 1);
open_video(oc, video_st);
// open the output file
if (url_fopen(&oc->pb, ofilename, URL_WRONLY) < 0) {
fprintf(stderr, "could not open '%s'\n", ofilename);
exit(1);
}
// write the stream header
av_write_header(oc);
for (i = 0 ; i < STREAM_NB_FRAMES*2 ; i++) {
write_video_frame(oc, video_st);
}
write_seq_end(oc, video_st);
close_video(oc, video_st);
av_write_trailer(oc);
// cleanup
for(i = 0; i < oc->nb_streams; i++) {
av_freep(&oc->streams[i]);
}
// program end
video_outbuf[0] = 0x00;
video_outbuf[1] = 0x00;
video_outbuf[2] = 0x01;
video_outbuf[3] = 0xB9;
put_buffer(&oc->pb, video_outbuf, 4);
put_flush_packet(&oc->pb);
url_fclose(&oc->pb);
av_free(oc);
av_free(video_outbuf);
return 0;
}
|
| Note: Line 13 and 16 of the Makefile below must begin with a TAB. The browser probably converted them to 8 spaces. If your make fails, then you may need to change 8 spaces to tabs. |
Makefile
include ../config.mak
VPATH=$(SRC_PATH)
CFLAGS=$(OPTFLAGS) -I. -I$(SRC_PATH) -I$(SRC_PATH)/libavcodec \
-I$(SRC_PATH)/libavformat -D_FILE_OFFSET_BITS=64 \
-D_LARGEFILE_SOURCE -D_GNU_SOURCE
FFLIBS = -L../libavformat -lavformat -L../libavcodec -lavcodec
default: mkloop
mkloop: mkloop.o
$(CC) -o mkloop $< $(FFLIBS) $(EXTRALIBS)
mkloop.o: mkloop.c
$(CC) $(CFLAGS) $(SDL_CFLAGS) -c -o $@ $<
|
The MPEG should have the following characteristics, In the Video Sequence Header:
The chroma format must be 4:2:0.
Video should be encoded as MP@ML.
Video background alignment may not be pixel perfect on different TiVo boxes or even between software releases. Therefore application developers should not rely upon the exact alignment of a video background. That said you can free up over 600kb of graphics memory by using a video background.