FFMPEG and SDL

Started by
12 comments, last by MichaelBarth 11 years, 11 months ago
To simplify what I need to do is to create a RGB surface from an AVFrame. SDL has a function just for this:


SDL_Surface *SDL_CreateRGBSurfaceFrom(void *pixels,
int width, int height, int depth, int pitch,
Uint32 Rmask, Uint32 Gmask, Uint32 Bmask, Uint32 Amask);


The function is straightforward, I just don't know how to get from an AVFrame to raw pixel data readable into this function. I've never been good with this. The Rmask, Gmask, Bmask stuff I believe has to do with the byte order. For another similar function which is SDL_CreateRGBSurface, SDL has an example which includes the following:


/* Create a 32-bit surface with the bytes of each pixel in R,G,B,A order,
as expected by OpenGL for textures */
SDL_Surface *surface;
Uint32 rmask, gmask, bmask, amask;
/* SDL interprets each pixel as a 32-bit number, so our masks must depend
on the endianness (byte order) of the machine */
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
rmask = 0xff000000;
gmask = 0x00ff0000;
bmask = 0x0000ff00;
amask = 0x000000ff;
#else
rmask = 0x000000ff;
gmask = 0x0000ff00;
bmask = 0x00ff0000;
amask = 0xff000000;
#endif
. . .
. . .
. . .
Advertisement
You can use [font=courier new,courier,monospace]sws_scale[/font] to convert the [font=courier new,courier,monospace]AVFrame[/font] to an RGB24/RGBA32 frame ([font=courier new,courier,monospace]PIX_FMT_RGB24[/font] or [font=courier new,courier,monospace]PIX_FMT_RGBA[/font]), similar to how you're already doing it. I don't have time right now to post some sample code, but I'll try and do that in the morning. The RGB pixel data will be stored in the [font=courier new,courier,monospace]AVFrame[/font]'s [font=courier new,courier,monospace]data[0][/font] member, in the format: [r, g, b, r, g, b, r, g, b, ...] or [r, g, b, a, r, g, b, a, r, g, b, a, ...] (regardless of endianness). The [font=courier new,courier,monospace]AVFrame[/font]'s [font=courier new,courier,monospace]linesize[0][/font] member will tell you how many bytes are in each row of pixel data in the frame (it'll be at least 3 * the frame's width (or 4 * the frame's with, if RGBA), but it can be slightly more (for padding... they do this so they can use certain SIMD instructions for optimizations)). You should be able to create a byte buffer the size of width * height * 3 (or * 4 if RGBA), and copy each row of pixels in the [font=courier new,courier,monospace]AVFrame[/font] to this buffer that you can pass to [font=courier new,courier,monospace]SDL_CreateRGBSurfaceFrom()[/font]. I'll post a sample tomorrow. Here's some small psuedo-code until I can write some proper stuff tomorrow (it's C++, sorry, but the concept is there):


char *buffer = new char[frameWidth * frameHeight];
AVFrame *frame = yourRgb24Frame;
for (int i = 0; i < frameHeight; ++i)
{
// The primary purpose I don't just copy the frame->data[0] straight into the buffer is because frame->linesize[0] may not equal
// the frame's width
std::copy(frame->data[0] + i * frame->linesize[0], frame->data[0] + i * frame->linesize[0] + 3 * frameWidth, buffer + i * frameWidth);
}
SDL_CreateRGBSurfaceFrom(buffer, ...);


That code is for RGB24 pixel data, but I don't know if [font=courier new,courier,monospace]SDL_CreateRGBSurfaceFrom()[/font] expects a fourth alpha channel to be in the buffer. If so, the code will need to be modified (the simplest would probably be to use [font=courier new,courier,monospace]sws_scale[/font] to make a RGBA frame and replace the 3 from above to a 4).
[size=2][ I was ninja'd 71 times before I stopped counting a long time ago ] [ f.k.a. MikeTacular ] [ My Blog ] [ SWFer: Gaplessly looped MP3s in your Flash games ]
Alright, here's some code. It's not quite fully functional, but it has the basics of each step. You'll probably need to separate out each chunk of put it where it belongs. I'm not sure about how to use SDL, so you're kind of on your own there (unless someone else can comment on that). There could be some errors in this code sample, but it should help illustrate the idea. You could also safely make some alterations to this, but I'll leave that up to you. I went for simplicity in this small sample.

// I'm assuming you've already opened the video...
SwsContext* imageConvertContext = NULL;
int frameFinished = 0;
int videoStreamIndex = 0; // I'm making this up, of course
char* buffer;
AVPacket packet;
AVFrame* decodingFrame;
AVFrame* rgbFrame;

buffer = (char*)malloc(codecContext->width * codecContext->height);
decodingFrame = avcodec_alloc_frame();
rgbFrame = avcodec_alloc_frame();
av_init_packet(&packet);

// Use PIX_FMT_RGBA instead if you need rgba...
if (avpicture_alloc((AVPicture*)rgbFrame, PIX_FMT_RGB24, codecContext->width, codecContext->height) != 0)
{
// error
}

imageConvertContext = sws_getCachedContext(NULL, // current swscontext
codecContext->width, // source width
codecContext->height, // source height
codecContext->pix_fmt, // source format
codecContext->width, // dest width
codecContext->height, // dest height
PIX_FMT_RGB24, // dest format
SWS_FAST_BILINEAR, // flags
NULL, // source filter
NULL, // dest filter
NULL); // params

if (m_imageConvertContext == NULL)
{
// error...
}

// This is where the decoding and converting part go. I'll assume you've got a decoding loop already.
// This isn't quite a proper loop, but it has the important part of using sws_scale()
av_free_packet(&packet);
while (av_read_frame(formatContext, &packet) == 0)
{
if (packet.stream_index == videoStreamIndex)
{
avcodec_decode_video2(codecContext, decodingFrame, &frameFinished, &packet);
if (frameFinished)
{
sws_scale(imageConvertContext,
decodingFrame->data,
decodingFrame->linesize,
0,
decodingFrame->height,
rgbFrame->data,
rgbFrame->linesize);
break;
}

}

av_free_packet(&packet);
}

// Now copy the rgbFrame into the buffer
for (int i = 0; i < codecContext->height; ++i)
{
memcpy(buffer + i * codecContext->width, rgbFrame->data[0] + i * rgbFrame->linesize[0], codecContext->width * 3); // change the 3 to a 4 if doing rgba
}
SDL_CreateRGBSurfaceFrom(buffer, ...);

// Clean up when you're done... probably at the end of your program
sws_freeContext(imageConvertContext);
avpicture_free((AVPicture*)rgbFrame);
av_free(rgbFrame);
av_free(decodingFrame);
[size=2][ I was ninja'd 71 times before I stopped counting a long time ago ] [ f.k.a. MikeTacular ] [ My Blog ] [ SWFer: Gaplessly looped MP3s in your Flash games ]

Alright, here's some code. It's not quite fully functional, but it has the basics of each step. You'll probably need to separate out each chunk of put it where it belongs. I'm not sure about how to use SDL, so you're kind of on your own there (unless someone else can comment on that). There could be some errors in this code sample, but it should help illustrate the idea. You could also safely make some alterations to this, but I'll leave that up to you. I went for simplicity in this small sample.

// I'm assuming you've already opened the video...
SwsContext* imageConvertContext = NULL;
int frameFinished = 0;
int videoStreamIndex = 0; // I'm making this up, of course
char* buffer;
AVPacket packet;
AVFrame* decodingFrame;
AVFrame* rgbFrame;

buffer = (char*)malloc(codecContext->width * codecContext->height);
decodingFrame = avcodec_alloc_frame();
rgbFrame = avcodec_alloc_frame();
av_init_packet(&packet);

// Use PIX_FMT_RGBA instead if you need rgba...
if (avpicture_alloc((AVPicture*)rgbFrame, PIX_FMT_RGB24, codecContext->width, codecContext->height) != 0)
{
// error
}

imageConvertContext = sws_getCachedContext(NULL, // current swscontext
codecContext->width, // source width
codecContext->height, // source height
codecContext->pix_fmt, // source format
codecContext->width, // dest width
codecContext->height, // dest height
PIX_FMT_RGB24, // dest format
SWS_FAST_BILINEAR, // flags
NULL, // source filter
NULL, // dest filter
NULL); // params

if (m_imageConvertContext == NULL)
{
// error...
}

// This is where the decoding and converting part go. I'll assume you've got a decoding loop already.
// This isn't quite a proper loop, but it has the important part of using sws_scale()
av_free_packet(&packet);
while (av_read_frame(formatContext, &packet) == 0)
{
if (packet.stream_index == videoStreamIndex)
{
avcodec_decode_video2(codecContext, decodingFrame, &frameFinished, &packet);
if (frameFinished)
{
sws_scale(imageConvertContext,
decodingFrame->data,
decodingFrame->linesize,
0,
decodingFrame->height,
rgbFrame->data,
rgbFrame->linesize);
break;
}

}

av_free_packet(&packet);
}

// Now copy the rgbFrame into the buffer
for (int i = 0; i < codecContext->height; ++i)
{
memcpy(buffer + i * codecContext->width, rgbFrame->data[0] + i * rgbFrame->linesize[0], codecContext->width * 3); // change the 3 to a 4 if doing rgba
}
SDL_CreateRGBSurfaceFrom(buffer, ...);

// Clean up when you're done... probably at the end of your program
sws_freeContext(imageConvertContext);
avpicture_free((AVPicture*)rgbFrame);
av_free(rgbFrame);
av_free(decodingFrame);



Man, it's amazing to work with so much code, Just last year I couldn't possibly conceive of doing something like this... Ah well enough of that, I'm getting another segmentation fault when attempting to blit it to the screen.

I modified my struct with the appropriate variables I'll need to manipulate:


typedef struct SDL_Movie {
int videoStream, audioStream;
AVFormatContext* pFormatCtx;
AVCodecContext* pCodecCtx;
AVCodecContext* aCodecCtx;
AVCodec* pCodec;
AVCodec* aCodec;
AVFrame* pFrame;
AVFrame* pFrameRGB;
char* buffer;
} SDL_Movie;


And I've also modified my loading function to compensate for the change:


SDL_Movie *SDL_LoadMovie(const char *file) {
SDL_Movie *mov = (SDL_Movie*)malloc(sizeof(SDL_Movie));
int ret;
if ((ret = avformat_open_input(&mov->pFormatCtx, file, NULL, NULL)) < 0)
{
SDL_MOVIE_ERROR = 1;
return NULL;
}
if (avformat_find_stream_info(mov->pFormatCtx, NULL) < 0)
{
SDL_MOVIE_ERROR = 2;
return NULL;
}
int i;
mov->videoStream = -1;
mov->audioStream = -1;
for (i=0; i < mov->pFormatCtx->nb_streams; i++)
{
if (mov->pFormatCtx->streams->codec->codec_type == AVMEDIA_TYPE_VIDEO && mov->videoStream < 0)
mov->videoStream=i;
if (mov->pFormatCtx->streams->codec->codec_type == AVMEDIA_TYPE_AUDIO && mov->audioStream < 0)
mov->audioStream = i;
}
if (mov->videoStream == -1)
{
SDL_MOVIE_ERROR = 3;
return NULL;
}
if (mov->audioStream == -1)
{
SDL_MOVIE_ERROR = 4;
return NULL;
}
mov->pCodecCtx = mov->pFormatCtx->streams[mov->videoStream]->codec;
mov->aCodecCtx = mov->pFormatCtx->streams[mov->audioStream]->codec;
if (!(mov->pCodec = avcodec_find_decoder(mov->pCodecCtx->codec_id)))
{
SDL_MOVIE_ERROR = 5;
return NULL;
}
if (!(mov->aCodec = avcodec_find_decoder(mov->aCodecCtx->codec_id)))
{
SDL_MOVIE_ERROR = 6;
return NULL;
}
if (avcodec_open(mov->pCodecCtx, mov->pCodec) < 0)
{
SDL_MOVIE_ERROR = 7;
return NULL;
}
avcodec_open(mov->aCodecCtx, mov->aCodec);
mov->pFrame = avcodec_alloc_frame();
mov->pFrameRGB = avcodec_alloc_frame();
if (!mov->pFrameRGB)
{
SDL_MOVIE_ERROR = 8;
return NULL;
}
//int numBytes = avpicture_get_size(PIX_FMT_RGB24, mov->pCodecCtx->width, mov->pCodecCtx->height);
mov->buffer = (char*)malloc(mov->pCodecCtx->width * mov->pCodecCtx->height);
//avpicture_fill((AVPicture *)mov->pFrameRGB, mov->buffer, PIX_FMT_RGB24, mov->pCodecCtx->width, mov->pCodecCtx->height);
if (avpicture_alloc((AVPicture*)mov->pFrameRGB, PIX_FMT_RGB24, mov->pCodecCtx->width, mov->pCodecCtx->height) != 0)
return NULL;
return mov;
}


Going back to my main function I created an SDL_Surface* bmp as well as the Uint32s for rmask, gmask, and etc. that were defined in my previous example. So here's where the conversion and stuff is actually taking place:


struct SwsContext* iCCtx = NULL;
int frameFinished;
AVPacket packet;
av_init_packet(&packet);
int i = 0;
iCCtx = sws_getCachedContext(NULL,
test->pCodecCtx->width,
test->pCodecCtx->height,
test->pCodecCtx->pix_fmt,
test->pCodecCtx->width,
test->pCodecCtx->height,
PIX_FMT_RGB24,
SWS_FAST_BILINEAR,
NULL,
NULL,
NULL);
if (!iCCtx)
return -1;
printf("While loop here:\n");
while (av_read_frame(test->pFormatCtx, &packet) >= 0) {
if (packet.stream_index == test->videoStream) {
avcodec_decode_video2(test->pCodecCtx, test->pFrame, &frameFinished, &packet);
printf("Decoding.\n");
SDL_Rect rect;
if (frameFinished) {
//SDL_LockYUVOverlay(bmp);
SDL_LockSurface(bmp);
//AVPicture pict;
/*
pict.data[0] = bmp->pixels[0];
pict.data[1] = bmp->pixels[2];
pict.data[2] = bmp->pixels[1];
pict.linesize[0] = bmp->pitches[0];
pict.linesize[1] = bmp->pitches[2];
pict.linesize[2] = bmp->pitches[1];
*/
//struct SwsContext* swsContext = sws_getContext(test->pCodecCtx->width, test->pCodecCtx->height, test->pCodecCtx->pix_fmt, test->pCodecCtx->width, test->pCodecCtx->height, PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);
sws_scale(iCCtx, test->pFrame->data, test->pFrame->linesize, 0, test->pFrame->height, test->pFrameRGB->data, test->pFrame->linesize);
//sws_scale(iCCtx, test->pFrame->data, test->pFrame->linesize, 0, test->pCodecCtx->height, pict.data, pict.linesize);
printf("SWSSCALE Passed\n");
//SDL_UnlockYUVOverlay(bmp);
rect.x = 0;
rect.y = 0;
rect.w = 1680;
rect.h = 1050;
//SDL_DisplayYUVOverlay(bmp, &rect);
printf("For loop here:\n");
for (i = 0; i < test->pCodecCtx->height; ++i)
{
memcpy(test->buffer + i * test->pCodecCtx->width, test->pFrameRGB->data[0] + i * test->pFrameRGB->linesize[0], test->pCodecCtx->width * 3); // change the 3 to a 4 if doing rgba
}
printf("End for loop.\n");
bmp = SDL_CreateRGBSurfaceFrom(test->buffer, test->pCodecCtx->width, test->pCodecCtx->height, 24, test->pFrameRGB->linesize, rmask, gmask, bmask, amask); //Creating RGB surface from buffer.
SDL_UnlockSurface(bmp);
printf("Surface created\n");
if (!bmp)
printf("%s\n", SDL_GetError());
SDL_BlitSurface(bmp, NULL, screen, NULL); //Blit SDL_Surface* bmp
printf("Blitted\n");
SDL_Flip(screen); //Updates screen
SDL_FillRect(screen, &rect, SDL_MapRGB(screen->format, 0, 0, 0)); //Clears screen to prevent nasty effects.
}
}
}


I assume I'm doing something wrong when I'm creating the RGB surface. If it would be any easier to you since you aren't familiar with SDL, I would be just fine with an OpenGL texture. The actual game I plan on doing will be in SDL_OpenGL. (basically OpenGL that instead of using GLUT, it uses SDL) I just need to play these two intro videos, and one of them with audio and both are at 720p. I just would've really liked to make something for SDL, but I guess I'm just not ready.

Either way, any help is appreciated.

This topic is closed to new replies.

Advertisement