Amazon Kinesis Video Streams から動画画像をリアルタイム受信して画像処理をおこなう (Java)

Kinesis Video Streams の API 呼び出しは AWS SDK for Java を、ストリームから MKV を解析するためのライブラリとして別途 Amazon Kinesis Video Streams Parser Library が提供されているので、これらを使って処理する方法です。

概要

参考) Amazon Kinesis Video Stream Parser Library

関連

AWS クライアントクラス (AWS SDK)

Kineis Video Streams は 3 つのカテゴリに分かれており、API によって異なる以下の Client クラスを使用します。ストリームに対する操作は API によって異なるデータエンドポイントを指定する必要があります。

  • AmazonKinesisVideoClient
  • AmazonKinesisVideoMediaClient
  • AmazonKinesisVideoArchivedMediaClient

例) GetMedia API の呼び出し

Regions region = Regions.AP_NORTHEAST_1;
String streamName = "stream-test";

// AWS 認証情報
AWSCredentialsProvider credentialsProvider = new ProfileCredentialsProvider();

// AmazonKinesisVideoClient を生成
AmazonKinesisVideoClient amazonKinesisVideo = AmazonKinesisVideoClientBuilder.standard()
        .withRegion(region)
        .withCredentials(credentialsProvider)
        .build();

// AmazonKinesisVideoMediaClient を生成 : GetDataEndpoint API でデータエンドポイントを取得して指定する
String endPoint = amazonKinesisVideo.getDataEndpoint(new GetDataEndpointRequest().withAPIName(APIName.GET_MEDIA).withStreamName(streamName)).getDataEndpoint();
AmazonKinesisVideoMediaClient amazonKinesisVideoMedia = AmazonKinesisVideoMediaClientBuilder.standard()
        .withEndpointConfiguration(new AwsClientBuilder.EndpointConfiguration(endPoint, region.getName()))
        .withCredentials(credentialsProvider)
        .build();

// GetMedia API 呼び出し
StartSelector startSelector = new StartSelector().withStartSelectorType(StartSelectorType.NOW);
GetMediaResult result = amazonKinesisVideoMedia.getMedia(new GetMediaRequest().withStreamName(streamName).withStartSelector(startSelector));

// ストリーム生成
InputStream inputStream = new InputStreamParserByteSource(result.getPayload());

StreamingMkvReader

ストリームから MKV エレメントを読み出すクラスです。Visitor クラス(後述)を指定することで読み出した MKV エレメントを処理します。

StreamingMkvReader mkvStreamReader = StreamingMkvReader.createDefault(inputStream);
mkvStreamReader.apply(visitor);

MkvElementVisitor

ストリームから読み出したセグメント、フレームを処理するクラスです。

CompositeMkvElementVisitor

複数の Visitor を束ねます。

CompositeMkvElementVisitor visitors = new CompositeMkvElementVisitor(visitor1, visitor2, ...);

OutputSegmentMerger

指定したストリームに出力します。

OutputSegmentMerger outputSegmentMerger = OutputSegmentMerger.createDefault(outputStream);

FrameVisitor

FrameVisitor.FrameProcessor を継承して処理を記述します。

class MyFrameProcessor implements FrameVisitor.FrameProcessor {
    @Override
    public void process(final Frame frame, final MkvTrackMetadata trackMetadata, final Optional<FragmentMetadata> fragmentMetadata) throws FrameProcessException {
        // TODO
    }
}

FrameVisitor frameVisitor = FrameVisitor.create(new MyFrameProcessor());

以下のデータが処理できます。

  • Frame : フレームデータ
  • trackNumber : トラック番号 (1~)
  • timeCode : タイムコード (セグメント先頭からの時間 (ms))
  • keyFrame : キーフレームかどうか
  • invisible
  • discardable
  • lacing
  • frameData : フレームデータ
  • MkvTrackMetadata : トラックに対するメタデータ
  • trackNumber : トラック番号 (1~)
  • trackUID [OPTIONAL]
  • trackName : kinesis_video
  • codecId : コーデック (例: "V_MPEG4/ISO/AVC")
  • codecName : コーデック名
  • pixelWidth [OPTIONAL] : 横ピクセル数
  • pixelHeight [OPTIONAL] : 縦ピクセル数
  • samplingFrequency [OPTIONAL]
  • channels [OPTIONAL]
  • bitDepth [OPTIONAL]
  • FragmentMetadata : フラグメントに対するメタデータ
  • fragmentNumberString : フラグメント番号 (String)
  • serverSideTimestampMillis : サーバータイムスタンプ (UNIX EPOCH (ms))
  • producerSideTimestampMillis : プロデューサータイムスタンプ (UNIX EPOCH (ms))
  • fragmentNumber : フラグメント番号 (BigInteger)
  • success : 成否 (boolean)
  • errorId : エラー番号 (long)
  • errorCode : エラーコード (String)
  • millisBehindNow [OPTIONAL]
  • continuationToken [OPTIONAL]

FrameProcessor には以下のものが用意されています。

H264FrameRenderer

映像をウインドウに表示する Visitor

KinesisVideoFrameViewer frameViewer = new KinesisVideoFrameViewer(1280, 720);
frameViewer.setVisible(true);

H264FrameRenderer frameRenderer = H264FrameRenderer.create(frameViewer);
FrameVisitor frameRenderVisitor = FrameVisitor.create(frameRenderer);

H264FrameDecoder

H.264 デコードして処理したい場合はこちらを継承します。

class MyFrameDecodeProcessor extends H264FrameDecoder {
    @Override
    public void process(final Frame frame, final MkvTrackMetadata trackMetadata, final Optional<FragmentMetadata> fragmentMetadata) throws FrameProcessException {
        BufferedImage image = decodeH264Frame(frame, trackMetadata);
        // TODO
    }
}

FrameVisitor frameVisitor = FrameVisitor.create(new MyFrameDecodeProcessor());

以下のようにすれば JPEG で保存できます。

Date d = new Date(fragmentMetadata.get().getProducerSideTimestampMillis() + frame.getTimeCode());
String dt = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX").format(d);
File outputFile = new File(String.format("output/%s.jpg", dt));
ImageIO.write(image, "jpg", outputfile);