1

我正在编写一个 python 扩展,它可以使用 libav 和 portaudio 播放音频。我有这个工作代码。但是,这是一个阻塞函数。我正在尝试使用 portaudio 回调函数来实现异步播放,但是我遇到了一个段错误,我不知道是什么原因造成的。

我目前有以下代码:

typedef struct {
    /* Python */
    PyObject_HEAD
    PyObject *filepath;
    PyObject *duration;
    PyObject *sample_rate;
    PyObject *channels;
    /* av */
    AVFormatContext *fmt_ctx;
    AVStream *audio_stream;
    AVCodecContext *codec_ctx;
    AVDictionaryEntry *current_tag; /* Used for iteration: for tag in song */
    /* portaudio */
    PaStream *pa_stream;
    unsigned int frame_count;
    unsigned int frame_index;
    unsigned int data_index;
    AVFrame *frames;
} Song;

...

#define PaPy_CHECK_ERROR(error) \
    if (error != paNoError) { \
        PyErr_SetString(PyExc_OSError, Pa_GetErrorText(error)); \
        return NULL; \
    }


static int pa_callback(const void *input_buffer,
                       void *output_buffer,
                       unsigned long frames_per_buffer,
                       const PaStreamCallbackTimeInfo* time_info,
                       PaStreamCallbackFlags status_flags,
                       void *user_data)
{
    Song *self = (Song *)user_data;
    unsigned int i = 0;
    int finished = 0;
    (void) input_buffer;
    (void) time_info;
    uint16_t *out = (uint16_t *)output_buffer;
    AVFrame frame = self->frames[self->frame_index];
    for (; i < frames_per_buffer; i++) {
        if (self->data_index++ > frame.nb_samples) {
            frame = self->frames[self->frame_index++];
            self->data_index = 0;
        }
        if (self->frame_index >= self->frame_count -1) {
            return -1;
        }
        *out++ = (*frame.data)[self->data_index];
    }
    return finished;
}

static PyObject *
Song_play(Song *self)
{
    AVCodec *codec = avcodec_find_decoder(self->audio_stream->codec->codec_id);
    if (codec == NULL) {
        return NULL;
    }
    if (avcodec_find_decoder(self->codec_ctx->codec_id) < 0) {
        return NULL;
    }
    if (avcodec_open2(self->codec_ctx, codec, NULL) < 0) {
        return NULL;
    }

    PaSampleFormat sample_fmt;
    switch (self->codec_ctx->sample_fmt) {
        case AV_SAMPLE_FMT_U8:
            sample_fmt = paUInt8;
            printf("uint 8\n");
            break;
        case AV_SAMPLE_FMT_S16:
            sample_fmt = paInt16;
            printf("uint 16\n");
            break;
        case AV_SAMPLE_FMT_S32:
            sample_fmt = paInt32;
            printf("int 16\n");
            break;
        case AV_SAMPLE_FMT_FLT:
            sample_fmt = paFloat32;
            printf("float\n");
            break;
        default:
            PyErr_SetString(PyExc_OSError,
                            "Unable to parse audio sample format.");
            return NULL;
    }
    PaError err = Pa_OpenDefaultStream(&self->pa_stream,
                                       0,
                                       self->codec_ctx->channels,
                                       sample_fmt,
                                       self->codec_ctx->sample_rate,
                                       paFramesPerBufferUnspecified,
                                       pa_callback,
                                       self);
    PaPy_CHECK_ERROR(err)
    AVPacket packet;
    self->frames = malloc(self->frame_count * sizeof(AVFrame));
    unsigned int i = 0;
    while (av_read_frame(self->fmt_ctx, &packet) >= 0) {
        if (packet.stream_index != self->audio_stream->index) {
            continue;
        }
        AVFrame frame;
        int got_frame;
        int ret = avcodec_decode_audio4(self->codec_ctx, &frame,
                                        &got_frame, &packet);
        if (ret < 0) {
            continue;
        }
        if (ret != packet.size) {
            continue;
        }
        if (got_frame) {
            self->frames[i] = frame;
            /* This worked, but it is a blocking call. */
            /*err = Pa_WriteStream(self->pa_stream, *frame.data,*/
            /*                     frame.nb_samples);*/
            /*PaPy_CHECK_ERROR(err)*/
            i++;
        }
/*        av_free_packet(&packet);*/
    }
    err = Pa_StartStream(self->pa_stream);
    PaPy_CHECK_ERROR(err)
    av_seek_frame(self->fmt_ctx, self->audio_stream->index, 0, 0);
    Py_RETURN_NONE;
}

但这只会给我带来噪音。完整的代码可以在这里看到。

有人可以告诉我这段代码有什么问题吗?

4

0 回答 0