I'm writing test code to make a video player in c++. I had a lot of issues with the sound making cracking sounds, so I though I'm not sending enough data to the audio thread. Then I got the code and tried to make a player without audio but the decoding was happening in another thread; while the main thread can just handle SDL2 windows and textures. But then I run the code the program just stops (not closed, so nothing happens but it is running) at a mutex lock with no errors.
my code:
extern "C"{
//FFmpeg libraries
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
//SDL2 libraries
#include <SDL2/SDL.h>
}
#ifdef __MINGW32__
#undef main /* Prevents SDL from overriding main() */
#endif
//C++ libraries
#include <stdio.h>
#include <iostream>
#include <chrono>
#include <thread>
#include <atomic>
#include <mutex>
#include <condition_variable>
#include <future>
// compatibility with newer API
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(55,28,1)
#define av_frame_alloc avcodec_alloc_frame
#define av_frame_free avcodec_free_frame
#endif
typedef struct PacketQueue {
AVPacketList *first_pkt, *last_pkt;
} PacketQueue;
AVCodecContext *pCodecCtx = nullptr;
PacketQueue videoq;
std::mutex mtx;
std::condition_variable convar;
std::atomic_int videoStream;
std::atomic<bool> quitting (false);
int packet_queue_put(PacketQueue *q, AVPacket *pkt){
AVPacketList *pkt1;
if(av_dup_packet(pkt) < 0){
return -1;
}
pkt1 = (AVPacketList*) av_malloc(sizeof(AVPacketList));
if(!pkt1){
return -1;
}
pkt1->pkt = *pkt;
pkt1->next = NULL;
std::lock_guard<std::mutex> lock(mtx);
if (!q->last_pkt){
q->first_pkt = pkt1;
}else{
q->last_pkt->next = pkt1;
}
q->last_pkt = pkt1;
convar.notify_all();
return 0;
}
static int packet_queue_get(PacketQueue *q, AVPacket *pkt, int block){
AVPacketList *pkt1;
int ret;
mtx.lock();
while(1){
if(quitting) {
ret = -1;
break;
}
pkt1 = q->first_pkt;
if(pkt1){
q->first_pkt = pkt1->next;
if(!q->first_pkt){
q->last_pkt = NULL;
}
*pkt = pkt1->pkt;
av_free(pkt1);
ret = 1;
break;
} else if (!block) {
ret = 0;
break;
} else {
std::unique_lock<std::mutex> lk(mtx, std::adopt_lock);
convar.wait(lk);
}
}
mtx.unlock();
return ret;
}
void decoderThreadFunc(char *fileurl){
AVFormatContext *pFormatCtx = nullptr;
AVCodecContext *pCodecCtxOrig = nullptr;
AVCodec *pCodec = nullptr;
AVPacket packet;
// Register all formats and codecs
av_register_all();
if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)){
fprintf(stderr, "Could not initialize SDL - %s\n", SDL_GetError());
goto CleanDecoder;
}
// Open video file
if(avformat_open_input(&pFormatCtx, fileurl, NULL, NULL) != 0){
fprintf(stderr, "Couldn't open file\n");
goto CleanDecoder; // Couldn't open file
}
// Retrieve stream information
if(avformat_find_stream_info(pFormatCtx, NULL) < 0){
fprintf(stderr, "Couldn't find stream info\n");
goto CleanDecoder; // Couldn't find stream information
}
// Find the first video stream
videoStream = av_find_best_stream(pFormatCtx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
if(videoStream < 0){
fprintf(stderr, "Couldn't not find a video stream\n");
goto CleanDecoder; // Didn't find a video stream
}
// Get a pointer to the codec context for the video stream
pCodecCtxOrig = pFormatCtx->streams[videoStream]->codec;
// Find the decoder for the video stream
pCodec = avcodec_find_decoder(pCodecCtxOrig->codec_id);
if(pCodec == NULL){
fprintf(stderr, "Couldn't find a codec\n");
goto CleanDecoder; // Codec not found
}
// Copy context
pCodecCtx = avcodec_alloc_context3(pCodec);
if(avcodec_copy_context(pCodecCtx, pCodecCtxOrig) != 0){
fprintf(stderr, "Couldn't copy codec context\n");
goto CleanDecoder; // Error copying codec context
}
// Open codec
if(avcodec_open2(pCodecCtx, pCodec, NULL) < 0){
fprintf(stderr, "Couldn't open codec\n");
goto CleanDecoder; // Could not open codec
}
// Init packet queue
memset(&videoq, 0, sizeof(PacketQueue));
convar.notify_all();
while(av_read_frame(pFormatCtx, &packet) >= 0 && !quitting){
// Is this a packet from the video stream?
if(packet.stream_index == videoStream){
packet_queue_put(&videoq, &packet);
}else {
// Free the packet that was allocated by av_read_frame
av_free_packet(&packet);
}
}
CleanDecoder:
quitting = true;
// Close the codec
avcodec_close(pCodecCtx);
avcodec_close(pCodecCtxOrig);
// Close the video file
avformat_close_input(&pFormatCtx);
}
int main(int argc, char *argv[]){
struct SwsContext *sws_ctx = nullptr;
AVFrame *pFrame = nullptr;
AVPacket packet;
int frameFinished;
SDL_Event event;
SDL_Window *screen;
SDL_Renderer *renderer;
SDL_Texture *texture;
Uint8 *yPlane, *uPlane, *vPlane;
size_t yPlaneSz, uvPlaneSz;
int uvPitch;
if(argc != 2){
fprintf(stderr, "Usage: %s <file>\n", argv[0]);
return 0;
}
std::thread decoderThread(decoderThreadFunc, argv[1]);
std::unique_lock<std::mutex> lk(mtx, std::adopt_lock);
convar.wait(lk);
if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)){
fprintf(stderr, "Could not initialize SDL - %s\n", SDL_GetError());
goto CleanMain;
}
// Init packet queue
memset(&videoq, 0, sizeof(PacketQueue));
// Make a screen to put our video
screen = SDL_CreateWindow("Video Player", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, pCodecCtx->width, pCodecCtx->height, 0);
if(!screen){
fprintf(stderr, "SDL: could not create window - exiting\n");
goto CleanMain;
}
renderer = SDL_CreateRenderer(screen, -1, 0);
if(!renderer){
fprintf(stderr, "SDL: could not create renderer - exiting\n");
goto CleanMain;
}
// Allocate a place to put our YUV image on that screen
texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12, SDL_TEXTUREACCESS_STREAMING, pCodecCtx->width, pCodecCtx->height);
if(!texture){
fprintf(stderr, "SDL: could not create texture - exiting\n");
goto CleanMain;
}
// Allocate video frame
pFrame = av_frame_alloc();
// initialize SWS context for software scaling
sws_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width,
pCodecCtx->height, PIX_FMT_YUV420P, SWS_BILINEAR, NULL, NULL, NULL);
// set up YV12 pixel array (12 bits per pixel)
yPlaneSz = pCodecCtx->width * pCodecCtx->height;
uvPlaneSz = pCodecCtx->width * pCodecCtx->height / 4;
yPlane = (Uint8*)malloc(yPlaneSz);
uPlane = (Uint8*)malloc(uvPlaneSz);
vPlane = (Uint8*)malloc(uvPlaneSz);
uvPitch = pCodecCtx->width / 2;
if(!yPlane || !uPlane || !vPlane){
fprintf(stderr, "Could not allocate pixel buffers - exiting\n");
goto CleanMain;
}
while(!quitting){
// Get packet
if(packet_queue_get(&videoq, &packet, 1) < 0){
continue;
}
// Is this a packet from the video stream?
if(packet.stream_index == videoStream){
// Decode video frame
avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);
// Did we get a video frame?
if(frameFinished){
AVPicture pict;
pict.data[0] = yPlane;
pict.data[1] = uPlane;
pict.data[2] = vPlane;
pict.linesize[0] = pCodecCtx->width;
pict.linesize[1] = uvPitch;
pict.linesize[2] = uvPitch;
// Convert the image into YUV format that SDL uses
sws_scale(sws_ctx, (uint8_t const * const *) pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pict.data, pict.linesize);
SDL_UpdateYUVTexture(texture, NULL, yPlane, pCodecCtx->width, uPlane, uvPitch, vPlane, uvPitch);
SDL_RenderClear(renderer);
SDL_RenderCopy(renderer, texture, NULL, NULL);
SDL_RenderPresent(renderer);
}
}
// Free the packet that was allocated by av_read_frame
av_free_packet(&packet);
SDL_PollEvent(&event);
switch(event.type){
case SDL_QUIT:
quitting = true;
break;
default:
break;
}
}
CleanMain:
quitting = true;
decoderThread.join();
//Cleaning up SDL2
SDL_DestroyTexture(texture);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(screen);
SDL_Quit();
// Free the YUV frame
av_frame_free(&pFrame);
free(yPlane);
free(uPlane);
free(vPlane);
return 0;
}
I'm using this site and this post as a guide.
Aucun commentaire:
Enregistrer un commentaire