[Armadillo:07472] Re: Armadillo-420 MJPG-streamer の画像乱れ問題について

Shin-ya Koga email@hidden
2011年 8月 13日 (土) 11:41:01 JST


サムシングプレシャスの古賀です。

小谷部さん([Armadillo:07470]):
>欠落したデータを調査しましたので、報告します。
> 
>こちらでも中村様が書かれているように、イメージデータの
>先頭付近が1箇所のみ欠落していました(100〜200バイトくら
>い)。
>その他、フレームデータの途中から取り込まれたもの(先頭に
>SOIマーカーが存在しない)や途中までしか取り込まれていないも
>の(末尾にEOIマーカーが存在しない)もありますので、単にカメラ
>からのデータをタイミングによって取りこぼしているだけではないか
>と思います。
>
>しかし、データ転送量が少ない場合に欠落の症状が見られないのは
>気になります(部屋を暗くし全体がぼやけたような映像を撮影した
>場合やFPSの設定を極端に小さくした場合など)。

フレームデータ片の欠落が発生するかどうかの要因の一つが、データ
転送量である、という観測結果ですよね。また、データ片の欠落は、
MJPEG フレームの先頭だけでなく、途中や末尾でも発生している、と。

で、linux-2.6.26-at13 に入っている UVC ドライバのソースを追って
みたところ、アイソクロナス転送のパケット落ちを検出した際のエラー
回復処理を修正することにより、改善できそうに思えます。改善とい
うのは、[Armadillo:07466] で書いた、

古賀([Armadillo:07466]):
>今回の場合の、安直な対応としては、UVC ドライバに手を入れて、
>アイソクロナス転送のパケット取りこぼしを検出した場合は、その
>フレームを捨ててしまうようにすることでしょうね。あるいは、

という方策です。


以下に述べる修正個所は、以前に中村さんが [Armadillo:07440] で
報告していらしたログとソースを見比べて気付きました。

中村さん([Armadillo:07440]):
>昨日のsyslogデータを見直しててみたら、こんなのが残ってました。
>たぶん
> # echo 255 > /sys/module/uvcvideo/parameters/trace
>とやったときのログだと思います。
>
>Jul 28 13:38:29 192.168.0.52 armadillo440-0 kernel: uvcvideo: Dequeuing
buffer 1 (3, 97091 bytes).
>Jul 28 13:38:29 192.168.0.52 armadillo440-0 kernel:
>Jul 28 13:38:29 192.168.0.52 armadillo440-0 last message repeated 4 time(s)


ここで、
 uvcvideo: USB isochronous frame lost (-18).
というメッセージは、uvc_video.c にある uvc_video_decode_isoc() で
出力しています。以下に、当該個所を引用します。

-------------------------ここから-------------------------
static void uvc_video_decode_isoc(struct urb *urb,
	struct uvc_video_device *video, struct uvc_buffer *buf)
{
    u8 *mem;
    int ret, i;

    for (i = 0; i < urb->number_of_packets; ++i) {
        if (urb->iso_frame_desc[i].status < 0) {
            uvc_trace(UVC_TRACE_FRAME, "USB isochronous frame "
                "lost (%d).\n", urb->iso_frame_desc[i].status);
            continue;  ★
        }
        …
}
-------------------------ここまで-------------------------

上の★の行では、アイソクロナス転送におけるパケット落ちを検出した
際、uvc_trace() でログ出力するだけで、パケット落ちの発生を記録す
ることは、していません。つまり、エラーの発生を無視しているわけで
す。

この部分と、他三ヶ所の実装を変更することにより、パケット落ちの
発生した MJPEG フレームを、UVC ドライバに捨てさせることができる
のではないかと思います。

以下に、必要と思われる変更箇所を記します。なお、以下に示した
ENABLE_DROP_INCOMPLETE_FRAME というコンパイルスイッチは、変更箇
所を分かりやすくするために勝手に作ったものです。目印として読んで
下さい。

(1)uvc_video.c の uvc_video_decode_isoc()
-------------------------ここから-------------------------
static void uvc_video_decode_isoc(struct urb *urb,
	struct uvc_video_device *video, struct uvc_buffer *buf)
{
    u8 *mem;
    int ret, i;

    for (i = 0; i < urb->number_of_packets; ++i) {
        if (urb->iso_frame_desc[i].status < 0) {
            uvc_trace(UVC_TRACE_FRAME, "USB isochronous frame "
                "lost (%d).\n", urb->iso_frame_desc[i].status);
#if ENABLE_DROP_INCOMPLETE_FRAME  ★
            buf->state = UVC_BUF_STATE_ERROR;  // mark as eror frame
#endif
            continue;
        }
        …
}
-------------------------ここまで-------------------------

(2)uvc_video.c の uvc_video_decode_start()
-------------------------ここから-------------------------
static int uvc_video_decode_start(struct uvc_video_device *video,
		struct uvc_buffer *buf, const __u8 *data, int len)
{
    …
    if (fid != video->last_fid && buf->buf.bytesused != 0) {
        uvc_trace(UVC_TRACE_FRAME, "Frame complete (FID bit "
            "toggled).\n");
#if ENABLE_DROP_INCOMPLETE_FRAME  ★
        if (buf->state != UVC_BUF_STATE_ERROR) {
            buf->state = UVC_BUF_STATE_DONE;
        }
#else
        buf->state = UVC_BUF_STATE_DONE;
#endif
        return -EAGAIN;
    }

    video->last_fid = fid;
    return data[0];
}
-------------------------ここまで-------------------------

(3)uvc_video.c の uvc_video_decode_end()
-------------------------ここから-------------------------
static void uvc_video_decode_end(struct uvc_video_device *video,
        struct uvc_buffer *buf, const __u8 *data, int len)
{
    /* Mark the buffer as done if the EOF marker is set. */
    if (data[1] & UVC_STREAM_EOF && buf->buf.bytesused != 0) {
        uvc_trace(UVC_TRACE_FRAME, "Frame complete (EOF found).\n");
        if (data[0] == len)
            uvc_trace(UVC_TRACE_FRAME, "EOF in empty payload.\n");
#if ENABLE_DROP_INCOMPLETE_FRAME  ★
        if (buf->state != UVC_BUF_STATE_ERROR) {
            buf->state = UVC_BUF_STATE_DONE;
        }
#else
        buf->state = UVC_BUF_STATE_DONE;
#endif
        if (video->dev->quirks & UVC_QUIRK_STREAM_NO_FID)
            video->last_fid ^= UVC_STREAM_FID;
    }
}
-------------------------ここまで-------------------------

(5)uvc_queue.c の uvc_queue_next_buffer()
-------------------------ここから-------------------------
struct uvc_buffer *uvc_queue_next_buffer(struct uvc_video_queue *queue,
        struct uvc_buffer *buf)
{
    struct uvc_buffer *nextbuf;
    unsigned long flags;

#if ENABLE_DROP_INCOMPLETE_FRAME  ★
    if (((queue->flags & UVC_QUEUE_DROP_INCOMPLETE) &&
        buf->buf.length != buf->buf.bytesused))
    || (buf->state == UVC_BUF_STATE_ERROR)) {
#else
    if ((queue->flags & UVC_QUEUE_DROP_INCOMPLETE) &&
        buf->buf.length != buf->buf.bytesused) {
#endif
        buf->state = UVC_BUF_STATE_QUEUED;
        buf->buf.bytesused = 0;
        return buf;
    }
    …
}
-------------------------ここまで-------------------------

上記の変更を加えることにより、アイソクロナス転送のパケット落ち
が発生したフレームデータは、UVC ヘッダの Frame ID が切り替わっ
た際(新しいフレームに切り替わった際)か、UVC ヘッダに EOF が
記録された際に、正常フレームをキューイングするタイミングで捨て
られると思います。

ところで、[Armadillo:07440] で中村さんが報告していらしたログの
うち、

>とか、
>
>Jul 28 13:38:32 192.168.0.52 armadillo440-0 kernel: uvcvideo: Dropping
payload (out of sync).
>Jul 28 13:38:32 192.168.0.52 armadillo440-0 last message repeated 89
time(s)

については、uvc_video_decode_start() で出していますが、このログ
が出るケースでは、アイソクロナス転送の受信パケットを捨ててエラー
快復するようになっていると思います。上記の変更を加えると、アイソ
クロナス転送のパケット落ちを検出した場合に、そのフレームに所属す
る後続のパケットが、このログを出している個所で全て捨てられるよう
になるでしょう。


以上、もし参考になりましたら幸いです。

--
古賀信哉 (株)サムシングプレシャス



armadillo メーリングリストの案内