lundi 24 juillet 2017

Is this deadlocked?

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