libvlcでファイルでなくメモリから動画や音楽を読み込む

# 始めに

libvlcでは動画などのメディアを読み込むための関数としてlibvlc_media_new_pathやlibvlc_media_new_fdなどが用意されていますが、これらはファイルやファイルディスクリプタを対象としたものでメモリ上のバイト列から直接読み込むための関数は用意されていません。すでにメモリ上に展開された動画、音楽をlibvlcで読み込むには汎用的に定義されたlibvlc_media_new_callbacksという関数を使用する必要があります。この関数の扱いについて詰まったので備忘録として記録しておきます。

# 環境

Ubuntu 22.04 LTS
libvlc 3.0.6
gcc 12.1.0

# 本題

libvlc3.0.6において関数libvlc_media_new_callbacksの宣言は次のようになっています。[^1]

[^1]: 2022/12/05現在開発中のlibvlc4.0.0から、引数のうちlibvlc_instance_t*が無くなるようです。また、その他の関数についても同様の変更があります。

```C
libvlc_media_t *libvlc_media_new_callbacks(libvlc_instance_t *inst,
                                           libvlc_media_open_cb open_cb,
                                           libvlc_media_read_cb read_cb,
                                           libvlc_media_seek_cb seek_cb,
                                           libvlc_media_close_cb close_cb,
                                           void *opaque);
```

それぞれの引数は次のようにします。

- inst: vlcのインスタンスへのポインタ
- open_cb: メディアを開く際のコールバック関数ポインタ
- read_cb: メディアを読む際のコールバック関数ポインタ
- seek_cb: メディアをシークする際のコールバック関数ポインタ
- close_cb: メディアを閉じる際のコールバック関数ポインタ
- opaque: 読み込むメディアへのポインタ

今から4つあるそれぞれのコールバックについて解説していきますがその前に、ここから先については簡単のためにopaqueが以下の構造体へのポインタであるものとして話を進めていきます。

```C
typedef struct {
    uint64_t size; // メディアのサイズ
    uint64_t pos; // メディアの読み込み位置のポジション
    unsigned char *data; // メディアの本体
} my_media;
```

## それぞれのコールバック関数の例

### open_cb

```C
int open_cb(void *opaque, void **datap, uint64_t *sizep) {
    // opaque: libvlc_media_new_callbacksでのopaque
    // datap: 他の関数でopaqueとして渡されるポインタのセット先
    // size: mediaのサイズのセット先

    mymedia *media = (media*)opaque;
    *datap = opaque;
    // サイズが不明な場合はUINT64_MAXをセットする
    *sizep = media->size;
    // posもセットしないと二回目の再生で壊れる
    media->pos = 0;    
    // 成功時に0を、失敗時は0以外の値を返す
    return 0;
}
```
[^2]

libvlc_media_new_callbacks引数のopen_cbとしてNULLポインタを渡すとdatapにはopaqueがセットされ、mediaのサイズは不明となります。

[^2]: datapにはメディアのバイト列そのものを渡すと勘違いし丸３日溶かしました。

### read_cb

```C
ssize_t read_cb(void *opaque, unsigned char *buf, size_t len) {
    // opaque: open_cbにてdatapにセットされたポインタ
    // buf: このポインタにlen分のmediaをコピーする
    // len: bufにコピーするメディアのサイズ

    my_media *media = (my_media*)opaque;

    // open_cbにてサイズを指定した場合でもそれを超えるようなlenが渡されるので注意
    if (len + media->pos > media->size) {
        len = media->size - media->pos;
    }

    memcpy(buf, media->data + media->pos, len);

    media->pos += len;

    // 返り値は実際に読み込んだデータサイズ、0のとき終わりまでの読み込みを、-1のときエラー発生を示す
    return len;
}
```

libvlc_media_new_callbacks引数のread_cbとしてNULLポインタを渡すことは出来ません。

### seek_cb

```C
int seek_cb(void *opaque, uint64_t offset) {
    // opaque: open_cbにてdatapにセットされたポインタ
    // offset: シーク先への絶対オフセット

     (my_media*)opaque->pos = offset;

    // 成功時に0を、失敗時に-1を返す
    return 0;
}
```

libvlc_media_new_callbacks引数のseek_cbとしてNULLポインタを渡すとシークはサポートされなくなります。

### close_cb

```C
void close_cb(void *opaque) {
    // opaque: open_cbにてdatapにセットされたポインタ

    return;
}
```

何もしない場合、libvlc_media_new_callbacks引数のclose_cbにはNULLポインタを渡しても問題ありません。

## 例

以上のことを踏まえた、メモリから音楽を読み込み5秒間再生するコードの例が以下になります。NULLチェックなどいろいろやってないのであくまでも参考程度に。

```C
#include <string.h>
#include <unistd.h>
#include <vlc/vlc.h>

typedef struct {
    uint64_t size; // メディアのサイズ
    uint64_t pos; // メディアの読み込み位置のオフセット
    unsigned char *data; // メディアの本体
} my_media;

int open_cb(void *opaque, void **datap, uint64_t *sizep) {
    // opaque: libvlc_media_new_callbacksでのopaque
    // datap: 他の関数でopaqueとして渡されるポインタのセット先
    // size: mediaのサイズのセット先

    mymedia *media = (media*)opaque;
    *datap = opaque;
    // サイズが不明な場合はUINT64_MAXをセットする
    *sizep = media->size;
    // posもセットしないと二回目の再生で壊れる
    media->pos = 0;    
    // 成功時に0を、失敗時は0以外の値を返す
    return 0;
}

ssize_t read_cb(void *opaque, unsigned char *buf, size_t len) {
    // opaque: open_cbにてdatapにセットされたポインタ
    // buf: このポインタにlen分のmediaをコピーする
    // len: bufにコピーするメディアのサイズ

    my_media *media = (my_media*)opaque;

    // open_cbにてサイズを指定した場合でもそれ以上読み込むようなlenが渡されるので注意
    if (len + media->pos > media->size) {
        len = media->size - media->pos;
    }

    memcpy(buf, media->data + media->pos, len);

    media->pos += len;

    // 返り値は実際に読み込んだデータサイズ、0のとき終わりまでの読み込みを、-1のときエラー発生を示す
    return len;
}

int seek_cb(void *opaque, uint64_t offset) {
    // opaque: open_cbにてdatapにセットされたポインタ
    // offset: シーク先への絶対オフセット

     ((my_media*)opaque)->pos = offset;

    // 成功時に0を、失敗時に-1を返す
    return 0;
}

void close_cb(void *opaque) {
    // opaque: open_cbにてdatapにセットされたポインタ

    return;
}

int main(int argc, const char *argv[]) {

    libvlc_instance_t *inst;
    libvlc_media_player_t *mp;
    libvlc_media_t *m;
    my_media media;

    inst = libvlc_new(argc, argv);

    hoge(&media); // メディアをmy_media構造体に配置

    m = libvlc_media_new_callbacks(inst, open_cb, read_cb, seek_cb, close_cb, &media);

    mp = libvlc_media_player_new_from_media(m);

    libvlc_media_release(m);

    libvlc_media_player_play(mp);

    sleep(5);

    libvlc_media_player_stop(mp);

    libvlc_media_player_release(mp);

    libvlc_release(inst);

    return 0;
}
```