diff --git a/.travis.yml b/.travis.yml index 1e805dd53..35c4afeb0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,9 @@ -dist: trusty sudo: required +addons: + apt: + update: true + env: - BAZEL_OS=linux BAZEL_VERSION=0.17.2 @@ -14,48 +17,100 @@ before_script: matrix: include: - - language: python + - name: "Ubuntu 14.04 Python 2.7" + dist: trusty + language: python python: - 2.7 before_install: + - sudo apt-get install -y -qq libav-tools - bash -x ${TRAVIS_BUILD_DIR}/.travis/python.configure.sh - pip install pyarrow==0.11.1 script: - - bazel test --show_progress=no -s --verbose_failures --test_output=errors -- //tensorflow_io/... - - language: python + - bazel test --noshow_progress --noshow_loading_progress --spawn_strategy standalone --verbose_failures --test_output=errors -- //tensorflow_io/... + - name: "Ubuntu 14.04 Python 3.4" + dist: trusty + language: python python: - 3.4 before_install: + - sudo apt-get install -y -qq libav-tools - bash -x ${TRAVIS_BUILD_DIR}/.travis/python.configure.sh script: - - bazel test --show_progress=no -s --verbose_failures --test_output=errors -- //tensorflow_io/... - - language: python + - bazel test --noshow_progress --noshow_loading_progress --spawn_strategy standalone --verbose_failures --test_output=errors -- //tensorflow_io/... + - name: "Ubuntu 14.04 Python 3.5" + dist: trusty + language: python python: - 3.5 before_install: + - sudo apt-get install -y -qq libav-tools - bash -x ${TRAVIS_BUILD_DIR}/.travis/python.configure.sh - pip install pyarrow==0.11.1 script: - - bazel test --show_progress=no -s --verbose_failures --test_output=errors -- //tensorflow_io/... - - language: python + - bazel test --noshow_progress --noshow_loading_progress --spawn_strategy standalone --verbose_failures --test_output=errors -- //tensorflow_io/... + - name: "Ubuntu 14.04 Python 3.6" + dist: trusty + language: python python: - 3.6 before_install: + - sudo apt-get install -y -qq libav-tools - bash -x ${TRAVIS_BUILD_DIR}/.travis/python.configure.sh - pip install pyarrow==0.11.1 script: - - bazel test --show_progress=no -s --verbose_failures --test_output=errors -- //tensorflow_io/... - - language: r + - bazel test --noshow_progress --noshow_loading_progress --spawn_strategy standalone --verbose_failures --test_output=errors -- //tensorflow_io/... + - name: "Ubuntu 16.04 Python 2.7 (Video Only)" + dist: xenial + language: python + python: + - 2.7 + before_install: + - sudo apt-get install -y -qq libav-tools + - bash -x ${TRAVIS_BUILD_DIR}/.travis/python.configure.sh + script: + - bazel test --noshow_progress --noshow_loading_progress --spawn_strategy standalone --verbose_failures --test_output=errors -- //tensorflow_io/video:video_py_test + - name: "Ubuntu 16.04 Python 3.5 (Video Only)" + dist: xenial + language: python + python: + - 3.5 + before_install: + - sudo apt-get install -y -qq libav-tools + - bash -x ${TRAVIS_BUILD_DIR}/.travis/python.configure.sh + script: + - bazel test --noshow_progress --noshow_loading_progress --spawn_strategy standalone --verbose_failures --test_output=errors -- //tensorflow_io/video:video_py_test + - name: "Ubuntu 18.04 Python 2.7 (Video Only)" + dist: trusty + language: python + python: + - 2.7 + script: + - docker run -i -t -e PYTHON_VERSION= -e BAZEL_VERSION=0.20.0 -e BAZEL_OS=linux --rm -v $PWD:/workspace -w /workspace --net=host ubuntu:18.04 bash -x -c ".travis/python.ubuntu.18.04.sh && apt-get -y -qq install libavformat57 libavcodec57 libavutil55 libswscale4 && bazel test --noshow_progress --noshow_loading_progress --spawn_strategy standalone --verbose_failures -- //tensorflow_io/video:video_py_test" + - name: "Ubuntu 18.04 Python 3.6 (Video Only)" + dist: trusty + language: python + python: + - 3.6 + script: + - docker run -i -t -e PYTHON_VERSION=3 -e BAZEL_VERSION=0.20.0 -e BAZEL_OS=linux --rm -v $PWD:/workspace -w /workspace --net=host ubuntu:18.04 bash -x -c ".travis/python.ubuntu.18.04.sh && apt-get -y -qq install libavformat57 libavcodec57 libavutil55 libswscale4 && bazel test --noshow_progress --noshow_loading_progress --spawn_strategy standalone --verbose_failures -- //tensorflow_io/video:video_py_test" + - name: "Ubuntu 14.04 R 3.2" + dist: trusty + language: r r: - 3.2 before_install: + - sudo apt-get install -y -qq libav-tools - cd R-package script: - R -e "devtools::test()" - - language: r + - name: "Ubuntu 14.04 R 3.5" + dist: trusty + language: r r: - 3.5 before_install: + - sudo apt-get install -y -qq libav-tools - cd R-package script: - R -e "devtools::test()" diff --git a/.travis/python.ubuntu.18.04.sh b/.travis/python.ubuntu.18.04.sh new file mode 100755 index 000000000..c49833648 --- /dev/null +++ b/.travis/python.ubuntu.18.04.sh @@ -0,0 +1,20 @@ +set -x -e + +# Test on Travis inside Ubuntu18.04 docker image +apt-get -y -qq update +apt-get -y -qq install python${PYTHON_VERSION}-pip curl wget unzip make +if [[ "" != "${PYTHON_VERSION}" ]]; then + ln -s /usr/bin/python${PYTHON_VERSION} /usr/bin/python + ln -s /usr/bin/pip${PYTHON_VERSION} /usr/bin/pip +fi +# Show gcc and python version in Travis CI +gcc -v +python --version +# Install bazel +URL="https://github.com/bazelbuild/bazel/releases/download/${BAZEL_VERSION}/bazel-${BAZEL_VERSION}-installer-${BAZEL_OS}-x86_64.sh" +wget -O install.sh "${URL}" +chmod +x install.sh +./install.sh +rm -f install.sh +# Configure TensorFlow +./configure.sh diff --git a/BUILD b/BUILD index bfefd0988..765d56619 100644 --- a/BUILD +++ b/BUILD @@ -13,5 +13,6 @@ sh_binary( "//tensorflow_io/parquet:parquet_py", "//tensorflow_io/libsvm:libsvm_py", "//tensorflow_io/image:image_py", + "//tensorflow_io/video:video_py", ], ) diff --git a/WORKSPACE b/WORKSPACE index 9f87b34d2..093dcb6cd 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -144,3 +144,36 @@ http_archive( sha256 = "12a13686cab7ffaf8ea01711b8f55e1dbd3bf059b7c46a25fefa1250bdd9dd23", strip_prefix = "flatbuffers-b99332efd732e6faf60bb7ce1ce5902ed65d5ba3", ) + +http_archive( + name = "ffmpeg_2_8", + urls = [ + "https://mirror.bazel.build/github.com/FFmpeg/FFmpeg/archive/n2.8.15.tar.gz", + "https://github.com/FFmpeg/FFmpeg/archive/n2.8.15.tar.gz", + ], + sha256 = "8ba1b91a14431fe37091936c3a34469d7473965ab9edde0343c88f2d920bd918", + strip_prefix = "FFmpeg-n2.8.15", + build_file = "//third_party:ffmpeg_2_8.BUILD", +) + +http_archive( + name = "ffmpeg_3_4", + urls = [ + "https://mirror.bazel.build/github.com/FFmpeg/FFmpeg/archive/n3.4.4.tar.gz", + "https://github.com/FFmpeg/FFmpeg/archive/n3.4.4.tar.gz", + ], + sha256 = "bbccc87cd031498728bcc2dba5596a47e6fd92b2cec060a71feef65617a261fe", + strip_prefix = "FFmpeg-n3.4.4", + build_file = "//third_party:ffmpeg_3_4.BUILD", +) + +http_archive( + name = "libav_9_20", + urls = [ + "https://mirror.bazel.build/github.com/libav/libav/archive/v9.20.tar.gz", + "https://github.com/libav/libav/archive/v9.20.tar.gz", + ], + sha256 = "ecc2389bc857602450196c9240e1ebc59066980f5d42e977efe0f498145775d4", + strip_prefix = "libav-9.20", + build_file = "//third_party:libav_9_20.BUILD", +) diff --git a/configure.sh b/configure.sh index 45e8f4e56..5c95dd22a 100755 --- a/configure.sh +++ b/configure.sh @@ -21,7 +21,7 @@ function write_action_env_to_bazelrc() { write_to_bazelrc "build --action_env $1=\"$2\"" } -rm .bazelrc +rm -f .bazelrc if python -c "import tensorflow" &> /dev/null; then echo 'using installed tensorflow' else diff --git a/tensorflow_io/video/BUILD b/tensorflow_io/video/BUILD new file mode 100644 index 000000000..6d4d76c44 --- /dev/null +++ b/tensorflow_io/video/BUILD @@ -0,0 +1,103 @@ +licenses(["notice"]) # Apache 2.0 + +package(default_visibility = ["//visibility:public"]) + +filegroup( + name = "test_data", + srcs = glob(["python/kernel_tests/testdata/*"]), +) + +cc_binary( + name = 'python/ops/_video_ops_ffmpeg_3.4.so', + srcs = [ + "kernels/ffmpeg_3_4.cc", + "kernels/video_reader.h", + "kernels/video_dataset_ops.cc", + "ops/dataset_ops.cc", + ], + includes = ["."], + linkshared = 1, + deps = [ + "@local_config_tf//:libtensorflow_framework", + "@local_config_tf//:tf_header_lib", + "@ffmpeg_3_4//:ffmpeg", + ], + copts = ["-pthread", "-std=c++11", "-D_GLIBCXX_USE_CXX11_ABI=0", "-DNDEBUG"] +) + +cc_binary( + name = 'python/ops/_video_ops_ffmpeg_2.8.so', + srcs = [ + "kernels/ffmpeg_2_8.cc", + "kernels/video_reader.h", + "kernels/video_dataset_ops.cc", + "ops/dataset_ops.cc", + ], + includes = ["."], + linkshared = 1, + deps = [ + "@local_config_tf//:libtensorflow_framework", + "@local_config_tf//:tf_header_lib", + "@ffmpeg_2_8//:ffmpeg", + ], + copts = ["-pthread", "-std=c++11", "-D_GLIBCXX_USE_CXX11_ABI=0", "-DNDEBUG"] +) + +cc_binary( + name = 'python/ops/_video_ops_libav_9.20.so', + srcs = [ + "kernels/libav_9_20.cc", + "kernels/video_reader.h", + "kernels/video_dataset_ops.cc", + "ops/dataset_ops.cc", + ], + includes = ["."], + linkshared = 1, + deps = [ + "@local_config_tf//:libtensorflow_framework", + "@local_config_tf//:tf_header_lib", + "@libav_9_20//:libav", + ], + copts = ["-pthread", "-std=c++11", "-D_GLIBCXX_USE_CXX11_ABI=0", "-DNDEBUG"] +) + +py_library( + name = "video_ops_py", + srcs = ([ + "python/ops/video_dataset_ops.py", + ]), + data = [ + ":python/ops/_video_ops_ffmpeg_3.4.so", + ":python/ops/_video_ops_ffmpeg_2.8.so", + ":python/ops/_video_ops_libav_9.20.so", + ], + srcs_version = "PY2AND3", +) + +py_test( + name = "video_py_test", + srcs = [ + "python/kernel_tests/video_test.py" + ], + main = "python/kernel_tests/video_test.py", + data = [ + ":test_data", + ], + deps = [ + ":video_ops_py", + ], + srcs_version = "PY2AND3", +) + +py_library( + name = "video_py", + srcs = ([ + "__init__.py", + "python/__init__.py", + "python/ops/__init__.py", + ]), + deps = [ + ":video_ops_py" + ], + srcs_version = "PY2AND3", +) diff --git a/tensorflow_io/video/__init__.py b/tensorflow_io/video/__init__.py new file mode 100644 index 000000000..178cdc85a --- /dev/null +++ b/tensorflow_io/video/__init__.py @@ -0,0 +1,32 @@ +# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Video Dataset. + +@@VideoDataset +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from tensorflow_io.video.python.ops.video_dataset_ops import VideoDataset + +from tensorflow.python.util.all_util import remove_undocumented + +_allowed_symbols = [ + "VideoDataset", +] + +remove_undocumented(__name__) diff --git a/tensorflow_io/video/kernels/ffmpeg_2_8.cc b/tensorflow_io/video/kernels/ffmpeg_2_8.cc new file mode 100644 index 000000000..385a54d29 --- /dev/null +++ b/tensorflow_io/video/kernels/ffmpeg_2_8.cc @@ -0,0 +1,164 @@ +/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include "tensorflow/core/framework/dataset.h" +#include "tensorflow/core/lib/io/buffered_inputstream.h" +#include "tensorflow/core/platform/file_system.h" + + +extern "C" { + +#include "libavcodec/avcodec.h" +#include "libavformat/avformat.h" +#include "libavutil/imgutils.h" +#include "libswscale/swscale.h" +#include + +} + +#include "kernels/video_reader.h" + +namespace tensorflow { +namespace data { +namespace video { + +Status VideoReader::ReadHeader() +{ + // Open input file, and allocate format context + if (avformat_open_input(&format_context_, filename_.c_str(), NULL, NULL) < 0) { + return errors::InvalidArgument("could not open video file: ", filename_); + } + // Retrieve stream information + if (avformat_find_stream_info(format_context_, NULL) < 0) { + return errors::InvalidArgument("could not find stream information: ", filename_); + } + // Find video stream + if ((stream_index_ = av_find_best_stream(format_context_, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0)) < 0) { + return errors::InvalidArgument("could not find video stream: ", filename_); + } + + AVStream *video_stream = format_context_->streams[stream_index_]; + codec_context_ = video_stream->codec; + // Find decoder for the stream + AVCodec *codec = avcodec_find_decoder(codec_context_->codec_id); + if (!codec) { + return errors::Internal("could not find video codec: ", codec_context_->codec_id); + } + // Initialize the decoders + // TODO (yongtang): avcodec_open2 is not thread-safe + AVDictionary *opts = NULL; + if (avcodec_open2(codec_context_, codec, &opts) < 0) { + return errors::Internal("could not open codec"); + } + + // Allocate frame + frame_ = av_frame_alloc(); + if (!frame_) { + return errors::Internal("could not allocate frame"); + } + + // Initialize packet + av_init_packet(&packet_); + packet_.data = NULL; + packet_.size = 0; + + // create scaling context + sws_context_ = sws_getContext(codec_context_->width, codec_context_->height, codec_context_->pix_fmt, codec_context_->width, codec_context_->height, AV_PIX_FMT_RGB24, 0, NULL, NULL, NULL); + if (!sws_context_) { + return errors::Internal("could not allocate sws context"); + } + frame_rgb_ = av_frame_alloc(); + if (!frame_rgb_) { + return errors::Internal("could not allocate rgb frame"); + } + // Determine required buffer size and allocate buffer + num_bytes_ = av_image_get_buffer_size(AV_PIX_FMT_RGB24, codec_context_->width, codec_context_->height, 1); + buffer_rgb_ = (uint8_t *)av_malloc(num_bytes_ * sizeof(uint8_t)); + avpicture_fill((AVPicture *)frame_rgb_, buffer_rgb_, AV_PIX_FMT_RGB24, codec_context_->width, codec_context_->height); + + frame_more_ = true; + packet_more_ = false; + buffer_more_ = ReadAhead(true); + + return Status::OK(); + } + +bool VideoReader::ReadAhead(bool first) +{ + while (packet_more_ || frame_more_) { + while (packet_more_) { + packet_more_ = false; + if (packet_.stream_index == stream_index_) { + int got_frame = 0; + int decoded = avcodec_decode_video2(codec_context_, frame_, &got_frame, &packet_); + if (!frame_more_ && got_frame) { + // This is the cached packet. + sws_scale(sws_context_, frame_->data, frame_->linesize, 0, codec_context_->height, frame_rgb_->data, frame_rgb_->linesize); + packet_more_ = true; + return true; + } + if (decoded >= 0 && got_frame) { + sws_scale(sws_context_, frame_->data, frame_->linesize, 0, codec_context_->height, frame_rgb_->data, frame_rgb_->linesize); + if (packet_.data) { + packet_.data += decoded; + packet_.size -= decoded; + packet_more_ = (packet_.size > 0); + } + return true; + } + } + } + if (frame_more_) { + // If this is not the first time, unref the packet + av_packet_unref(&packet_); + frame_more_ = (av_read_frame(format_context_, &packet_) == 0); + if (!frame_more_) { + // Flush out the cached packet + packet_more_ = true; + packet_.data = NULL; + packet_.size = 0; + } else { + // More packet to process + packet_more_ = true; + } + } + } + return false; + } + +Status VideoReader::ReadFrame(int *num_bytes, uint8_t**value, int *height, int *width) +{ + *height = codec_context_->height; + *width = codec_context_->width; + *num_bytes = num_bytes_; + if (buffer_more_) { + *value = buffer_rgb_; + buffer_more_ = ReadAhead(true); + return Status::OK(); + } + return errors::OutOfRange("EOF"); +} + +VideoReader::~VideoReader() { + av_free(buffer_rgb_); + av_frame_free(&frame_rgb_); + sws_freeContext(sws_context_); + av_frame_free(&frame_); + avformat_close_input(&format_context_); +} + +} // namespace +} // namespace data +} // namespace tensorflow diff --git a/tensorflow_io/video/kernels/ffmpeg_3_4.cc b/tensorflow_io/video/kernels/ffmpeg_3_4.cc new file mode 100644 index 000000000..1993120dd --- /dev/null +++ b/tensorflow_io/video/kernels/ffmpeg_3_4.cc @@ -0,0 +1,173 @@ +/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include "tensorflow/core/framework/dataset.h" +#include "tensorflow/core/lib/io/buffered_inputstream.h" +#include "tensorflow/core/platform/file_system.h" + + +extern "C" { + +#include "libavcodec/avcodec.h" +#include "libavformat/avformat.h" +#include "libavutil/imgutils.h" +#include "libswscale/swscale.h" +#include + +} + +#include "kernels/video_reader.h" + +namespace tensorflow { +namespace data { +namespace video { + +Status VideoReader::ReadHeader() +{ + // Open input file, and allocate format context + if (avformat_open_input(&format_context_, filename_.c_str(), NULL, NULL) < 0) { + return errors::InvalidArgument("could not open video file: ", filename_); + } + // Retrieve stream information + if (avformat_find_stream_info(format_context_, NULL) < 0) { + return errors::InvalidArgument("could not find stream information: ", filename_); + } + // Find video stream + if ((stream_index_ = av_find_best_stream(format_context_, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0)) < 0) { + return errors::InvalidArgument("could not find video stream: ", filename_); + } + + AVStream *video_stream = format_context_->streams[stream_index_]; + // Find decoder for the stream + AVCodec *codec = avcodec_find_decoder(video_stream->codecpar->codec_id); + if (!codec) { + return errors::Internal("could not find video codec: ", video_stream->codecpar->codec_id); + } + // Allocate a codec context for the decoder + codec_context_ = avcodec_alloc_context3(codec); + if (!codec_context_) { + return errors::Internal("could not allocate codec context"); + } + // Copy codec parameters from input stream to output codec context + if (avcodec_parameters_to_context(codec_context_, video_stream->codecpar) < 0) { + return errors::Internal("could not copy codec parameters from input stream to output codec context"); + } + // Initialize the decoders + // TODO (yongtang): avcodec_open2 is not thread-safe + AVDictionary *opts = NULL; + if (avcodec_open2(codec_context_, codec, &opts) < 0) { + return errors::Internal("could not open codec"); + } + + // Allocate frame + frame_ = av_frame_alloc(); + if (!frame_) { + return errors::Internal("could not allocate frame"); + } + + // Initialize packet + av_init_packet(&packet_); + packet_.data = NULL; + packet_.size = 0; + + // create scaling context + sws_context_ = sws_getContext(codec_context_->width, codec_context_->height, codec_context_->pix_fmt, codec_context_->width, codec_context_->height, AV_PIX_FMT_RGB24, 0, NULL, NULL, NULL); + if (!sws_context_) { + return errors::Internal("could not allocate sws context"); + } + frame_rgb_ = av_frame_alloc(); + if (!frame_rgb_) { + return errors::Internal("could not allocate rgb frame"); + } + // Determine required buffer size and allocate buffer + num_bytes_ = av_image_get_buffer_size(AV_PIX_FMT_RGB24, codec_context_->width, codec_context_->height, 1); + buffer_rgb_ = (uint8_t *)av_malloc(num_bytes_ * sizeof(uint8_t)); + avpicture_fill((AVPicture *)frame_rgb_, buffer_rgb_, AV_PIX_FMT_RGB24, codec_context_->width, codec_context_->height); + + frame_more_ = true; + packet_more_ = false; + buffer_more_ = ReadAhead(true); + + return Status::OK(); + } + +bool VideoReader::ReadAhead(bool first) +{ + while (packet_more_ || frame_more_) { + while (packet_more_) { + packet_more_ = false; + if (packet_.stream_index == stream_index_) { + int got_frame = 0; + int decoded = avcodec_decode_video2(codec_context_, frame_, &got_frame, &packet_); + if (!frame_more_ && got_frame) { + // This is the cached packet. + sws_scale(sws_context_, frame_->data, frame_->linesize, 0, codec_context_->height, frame_rgb_->data, frame_rgb_->linesize); + packet_more_ = true; + return true; + } + if (decoded >= 0 && got_frame) { + sws_scale(sws_context_, frame_->data, frame_->linesize, 0, codec_context_->height, frame_rgb_->data, frame_rgb_->linesize); + if (packet_.data) { + packet_.data += decoded; + packet_.size -= decoded; + packet_more_ = (packet_.size > 0); + } + return true; + } + } + } + if (frame_more_) { + // If this is not the first time, unref the packet + av_packet_unref(&packet_); + frame_more_ = (av_read_frame(format_context_, &packet_) == 0); + if (!frame_more_) { + // Flush out the cached packet + packet_more_ = true; + packet_.data = NULL; + packet_.size = 0; + } else { + // More packet to process + packet_more_ = true; + } + } + } + return false; + } + +Status VideoReader::ReadFrame(int *num_bytes, uint8_t**value, int *height, int *width) +{ + *height = codec_context_->height; + *width = codec_context_->width; + *num_bytes = num_bytes_; + if (buffer_more_) { + *value = buffer_rgb_; + buffer_more_ = ReadAhead(true); + return Status::OK(); + } + return errors::OutOfRange("EOF"); +} + +VideoReader::~VideoReader() { + av_free(buffer_rgb_); + av_frame_free(&frame_rgb_); + sws_freeContext(sws_context_); + av_frame_free(&frame_); + avcodec_free_context(&codec_context_); + avformat_close_input(&format_context_); +} + +} // namespace +} // namespace data +} // namespace tensorflow diff --git a/tensorflow_io/video/kernels/libav_9_20.cc b/tensorflow_io/video/kernels/libav_9_20.cc new file mode 100644 index 000000000..5ec63dea9 --- /dev/null +++ b/tensorflow_io/video/kernels/libav_9_20.cc @@ -0,0 +1,165 @@ +/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include "tensorflow/core/framework/dataset.h" +#include "tensorflow/core/lib/io/buffered_inputstream.h" +#include "tensorflow/core/platform/file_system.h" + + +extern "C" { + +#include "libavcodec/avcodec.h" +#include "libavformat/avformat.h" +#include "libavutil/imgutils.h" +#include "libswscale/swscale.h" +#include + +} + +#include "kernels/video_reader.h" + +namespace tensorflow { +namespace data { +namespace video { + +Status VideoReader::ReadHeader() +{ + // Open input file, and allocate format context + if (avformat_open_input(&format_context_, filename_.c_str(), NULL, NULL) < 0) { + return errors::InvalidArgument("could not open video file: ", filename_); + } + // Retrieve stream information + if (avformat_find_stream_info(format_context_, NULL) < 0) { + return errors::InvalidArgument("could not find stream information: ", filename_); + } + // Find video stream + if ((stream_index_ = av_find_best_stream(format_context_, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0)) < 0) { + return errors::InvalidArgument("could not find video stream: ", filename_); + } + + AVStream *video_stream = format_context_->streams[stream_index_]; + codec_context_ = video_stream->codec; + // Find decoder for the stream + AVCodec *codec = avcodec_find_decoder(codec_context_->codec_id); + if (!codec) { + return errors::Internal("could not find video codec: ", codec_context_->codec_id); + } + // Initialize the decoders + // TODO (yongtang): avcodec_open2 is not thread-safe + AVDictionary *opts = NULL; + if (avcodec_open2(codec_context_, codec, &opts) < 0) { + return errors::Internal("could not open codec"); + } + + // Allocate frame + frame_ = avcodec_alloc_frame(); + if (!frame_) { + return errors::Internal("could not allocate frame"); + } + + // Initialize packet + av_init_packet(&packet_); + packet_.data = NULL; + packet_.size = 0; + + // create scaling context + sws_context_ = sws_getContext(codec_context_->width, codec_context_->height, codec_context_->pix_fmt, codec_context_->width, codec_context_->height, AV_PIX_FMT_RGB24, SWS_BILINEAR, NULL, NULL, NULL); + if (!sws_context_) { + return errors::Internal("could not allocate sws context"); + } + frame_rgb_ = avcodec_alloc_frame(); + if (!frame_rgb_) { + return errors::Internal("could not allocate rgb frame"); + } + // Determine required buffer size and allocate buffer + num_bytes_ = avpicture_get_size(AV_PIX_FMT_RGB24, codec_context_->width, codec_context_->height); + buffer_rgb_ = (uint8_t *)av_malloc(num_bytes_ * sizeof(uint8_t)); + avpicture_fill((AVPicture *)frame_rgb_, buffer_rgb_, AV_PIX_FMT_RGB24, codec_context_->width, codec_context_->height); + + frame_more_ = true; + packet_more_ = false; + buffer_more_ = ReadAhead(true); + + return Status::OK(); + } + +bool VideoReader::ReadAhead(bool first) +{ + while (packet_more_ || frame_more_) { + while (packet_more_) { + packet_more_ = false; + if (packet_.stream_index == stream_index_) { + int got_frame = 0; + int decoded = avcodec_decode_video2(codec_context_, frame_, &got_frame, &packet_); + if (!frame_more_ && got_frame) { + // This is the cached packet. + sws_scale(sws_context_, frame_->data, frame_->linesize, 0, codec_context_->height, frame_rgb_->data, frame_rgb_->linesize); + packet_more_ = true; + return true; + } + if (decoded >= 0 && got_frame) { + sws_scale(sws_context_, frame_->data, frame_->linesize, 0, codec_context_->height, frame_rgb_->data, frame_rgb_->linesize); + if (packet_.data) { + packet_.data += decoded; + packet_.size -= decoded; + packet_more_ = (packet_.size > 0); + } + return true; + } + } + } + if (frame_more_) { + // If this is not the first time, unref the packet + // NOTE: libav 9.20 does not need unref or free here. + // av_packet_unref(&packet_); + frame_more_ = (av_read_frame(format_context_, &packet_) == 0); + if (!frame_more_) { + // Flush out the cached packet + packet_more_ = true; + packet_.data = NULL; + packet_.size = 0; + } else { + // More packet to process + packet_more_ = true; + } + } + } + return false; + } + +Status VideoReader::ReadFrame(int *num_bytes, uint8_t**value, int *height, int *width) +{ + *height = codec_context_->height; + *width = codec_context_->width; + *num_bytes = num_bytes_; + if (buffer_more_) { + *value = buffer_rgb_; + buffer_more_ = ReadAhead(true); + return Status::OK(); + } + return errors::OutOfRange("EOF"); +} + +VideoReader::~VideoReader() { + av_free(buffer_rgb_); + avcodec_free_frame(&frame_rgb_); + sws_freeContext(sws_context_); + avcodec_free_frame(&frame_); + avformat_close_input(&format_context_); +} + +} // namespace +} // namespace data +} // namespace tensorflow diff --git a/tensorflow_io/video/kernels/video_dataset_ops.cc b/tensorflow_io/video/kernels/video_dataset_ops.cc new file mode 100644 index 000000000..9d6852aa8 --- /dev/null +++ b/tensorflow_io/video/kernels/video_dataset_ops.cc @@ -0,0 +1,187 @@ +/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include "tensorflow/core/framework/dataset.h" +#include "tensorflow/core/lib/io/buffered_inputstream.h" +#include "tensorflow/core/platform/file_system.h" +#include "kernels/video_reader.h" + +namespace tensorflow { +namespace data { +namespace { + +static mutex mu(LINKER_INITIALIZED); +static unsigned count(0); +void VideoReaderInit() { + mutex_lock lock(mu); + count++; + if (count == 1) { + // Register all formats and codecs + av_register_all(); + } +} + +class VideoDatasetOp : public DatasetOpKernel { + public: + using DatasetOpKernel::DatasetOpKernel; + explicit VideoDatasetOp(OpKernelConstruction* ctx) + : DatasetOpKernel(ctx) { + } + void MakeDataset(OpKernelContext* ctx, DatasetBase** output) override { + const Tensor* filenames_tensor; + OP_REQUIRES_OK(ctx, ctx->input("filenames", &filenames_tensor)); + OP_REQUIRES( + ctx, filenames_tensor->dims() <= 1, + errors::InvalidArgument("`filenames` must be a scalar or a vector.")); + + std::vector filenames; + filenames.reserve(filenames_tensor->NumElements()); + for (int i = 0; i < filenames_tensor->NumElements(); ++i) { + filenames.push_back(filenames_tensor->flat()(i)); + } + + *output = new Dataset(ctx, filenames); + } + + private: + class Dataset : public DatasetBase { + public: + Dataset(OpKernelContext* ctx, const std::vector& filenames) + : DatasetBase(DatasetContext(ctx)), + filenames_(filenames) {} + + std::unique_ptr MakeIteratorInternal( + const string& prefix) const override { + return std::unique_ptr( + new Iterator({this, strings::StrCat(prefix, "::Video")})); + } + + const DataTypeVector& output_dtypes() const override { + static DataTypeVector* dtypes = new DataTypeVector({DT_UINT8}); + return *dtypes; + } + + const std::vector& output_shapes() const override { + static std::vector* shapes = + new std::vector({{-1, -1, 3}}); + return *shapes; + } + + string DebugString() const override { + return "VideoDatasetOp::Dataset"; + } + + protected: + Status AsGraphDefInternal(SerializationContext* ctx, + DatasetGraphDefBuilder* b, + Node** output) const override { + Node* filenames = nullptr; + TF_RETURN_IF_ERROR(b->AddVector(filenames_, &filenames)); + TF_RETURN_IF_ERROR(b->AddDataset(this, {filenames}, output)); + return Status::OK(); + } + + private: + class Iterator : public DatasetIterator { + public: + explicit Iterator(const Params& params) + : DatasetIterator(params) {} + + Status GetNextInternal(IteratorContext* ctx, + std::vector* out_tensors, + bool* end_of_sequence) override { + mutex_lock l(mu_); + do { + // We are currently processing a file, so try to read the next record. + if (reader_) { + int num_bytes, height, width; + uint8_t *value; + Status status = reader_->ReadFrame(&num_bytes, &value, &height, &width); + if (!errors::IsOutOfRange(status)) { + TF_RETURN_IF_ERROR(status); + + Tensor value_tensor(ctx->allocator({}), DT_UINT8, {height, width, 3}); + std::memcpy(reinterpret_cast(value_tensor.flat().data()), reinterpret_cast(value), num_bytes * sizeof(uint8_t)); + out_tensors->emplace_back(std::move(value_tensor)); + + *end_of_sequence = false; + return Status::OK(); + } + // We have reached the end of the current file, so maybe + // move on to next file. + ResetStreamsLocked(); + ++current_file_index_; + } + + // Iteration ends when there are no more files to process. + if (current_file_index_ == dataset()->filenames_.size()) { + *end_of_sequence = true; + return Status::OK(); + } + + TF_RETURN_IF_ERROR(SetupStreamsLocked(ctx->env())); + } while (true); + } + + protected: + Status SaveInternal(IteratorStateWriter* writer) override { + return errors::Unimplemented("SaveInternal is currently not supported"); + } + + Status RestoreInternal(IteratorContext* ctx, + IteratorStateReader* reader) override { + return errors::Unimplemented( + "RestoreInternal is currently not supported"); + } + + private: + // Sets up Video streams to read from the topic at + // `current_file_index_`. + Status SetupStreamsLocked(Env* env) EXCLUSIVE_LOCKS_REQUIRED(mu_) { + VideoReaderInit(); + + if (current_file_index_ >= dataset()->filenames_.size()) { + return errors::InvalidArgument( + "current_file_index_:", current_file_index_, + " >= filenames_.size():", dataset()->filenames_.size()); + } + + // Actually move on to next file. + const string& filename = dataset()->filenames_[current_file_index_]; + reader_.reset(new video::VideoReader(filename)); + return reader_->ReadHeader(); + return Status::OK(); + } + + // Resets all Video streams. + void ResetStreamsLocked() EXCLUSIVE_LOCKS_REQUIRED(mu_) { + reader_.reset(); + } + + mutex mu_; + size_t current_file_index_ GUARDED_BY(mu_) = 0; + std::unique_ptr reader_ GUARDED_BY(mu_); + }; + + const std::vector filenames_; + }; +}; + +REGISTER_KERNEL_BUILDER(Name("VideoDataset").Device(DEVICE_CPU), + VideoDatasetOp); + +} // namespace +} // namespace data +} // namespace tensorflow diff --git a/tensorflow_io/video/kernels/video_reader.h b/tensorflow_io/video/kernels/video_reader.h new file mode 100644 index 000000000..b8059b7c0 --- /dev/null +++ b/tensorflow_io/video/kernels/video_reader.h @@ -0,0 +1,66 @@ +/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include "tensorflow/core/framework/dataset.h" +#include "tensorflow/core/lib/io/buffered_inputstream.h" +#include "tensorflow/core/platform/file_system.h" + +extern "C" { + +#include "libavcodec/avcodec.h" +#include "libavformat/avformat.h" +#include "libavutil/imgutils.h" +#include "libswscale/swscale.h" +#include + +} + +namespace tensorflow { +namespace data { +namespace video { + +class VideoReader { + public: + explicit VideoReader(const string &filename) : filename_(filename) {} + + Status ReadHeader(); + + bool ReadAhead(bool first); + + Status ReadFrame(int *num_bytes, uint8_t**value, int *height, int *width); + + virtual ~VideoReader(); + + private: + std::string ahead_; + std::string filename_; + bool frame_more_ = false; + bool packet_more_ = false; + bool buffer_more_ = false; + int stream_index_ = -1; + size_t num_bytes_ = 0; + uint8_t *buffer_rgb_ = 0; + AVFrame *frame_rgb_ = 0; + struct SwsContext *sws_context_ = 0; + AVFormatContext *format_context_ = 0; + AVCodecContext *codec_context_ = 0; + AVFrame *frame_ = 0; + AVPacket packet_; + TF_DISALLOW_COPY_AND_ASSIGN(VideoReader); +}; + +} // namespace +} // namespace data +} // namespace tensorflow diff --git a/tensorflow_io/video/ops/dataset_ops.cc b/tensorflow_io/video/ops/dataset_ops.cc new file mode 100644 index 000000000..f819b6a4a --- /dev/null +++ b/tensorflow_io/video/ops/dataset_ops.cc @@ -0,0 +1,31 @@ +/* Copyright 2018 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include "tensorflow/core/framework/common_shape_fns.h" +#include "tensorflow/core/framework/op.h" +#include "tensorflow/core/framework/shape_inference.h" + +namespace tensorflow { + +REGISTER_OP("VideoDataset") + .Input("filenames: string") + .Output("handle: variant") + .SetIsStateful() + .SetShapeFn([](shape_inference::InferenceContext* c) { + c->set_output(0, c->MakeShape({c->UnknownDim(), c->UnknownDim(), 3})); + return Status::OK(); + }); + +} // namespace tensorflow diff --git a/tensorflow_io/video/python/__init__.py b/tensorflow_io/video/python/__init__.py new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/tensorflow_io/video/python/__init__.py @@ -0,0 +1 @@ + diff --git a/tensorflow_io/video/python/kernel_tests/testdata/small.mp4 b/tensorflow_io/video/python/kernel_tests/testdata/small.mp4 new file mode 100644 index 000000000..1fc478842 Binary files /dev/null and b/tensorflow_io/video/python/kernel_tests/testdata/small.mp4 differ diff --git a/tensorflow_io/video/python/kernel_tests/video_test.py b/tensorflow_io/video/python/kernel_tests/video_test.py new file mode 100644 index 000000000..e969203b0 --- /dev/null +++ b/tensorflow_io/video/python/kernel_tests/video_test.py @@ -0,0 +1,60 @@ +# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. +# ============================================================================== +"""Tests for VideoDataset.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import os + +from tensorflow.python.platform import test + +from tensorflow_io.video.python.ops import video_dataset_ops +from tensorflow.python.framework import constant_op +from tensorflow.python.framework import dtypes +from tensorflow.python.framework import errors +from tensorflow.python.ops import image_ops +from tensorflow.python.platform import resource_loader + + +class VideoDatasetTest(test.TestCase): + + def test_video_dataset(self): + """Test case for VideoDataset.""" + filename = os.path.join(resource_loader.get_data_files_path(), + "testdata", "small.mp4") + + filenames = constant_op.constant([filename], dtypes.string) + num_repeats = 2 + + dataset = video_dataset_ops.VideoDataset(filenames).repeat( + num_repeats) + iterator = dataset.make_initializable_iterator() + init_op = iterator.initializer + get_next = iterator.get_next() + + with self.cached_session() as sess: + sess.run(init_op) + for _ in range(num_repeats): # Dataset is repeated. + for _ in range(166): # 166 frames + v = sess.run(get_next) + self.assertAllEqual(v.shape, (320, 560, 3)) + with self.assertRaises(errors.OutOfRangeError): + sess.run(get_next) + + +if __name__ == "__main__": + test.main() diff --git a/tensorflow_io/video/python/ops/__init__.py b/tensorflow_io/video/python/ops/__init__.py new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/tensorflow_io/video/python/ops/__init__.py @@ -0,0 +1 @@ + diff --git a/tensorflow_io/video/python/ops/video_dataset_ops.py b/tensorflow_io/video/python/ops/video_dataset_ops.py new file mode 100644 index 000000000..7d10300b9 --- /dev/null +++ b/tensorflow_io/video/python/ops/video_dataset_ops.py @@ -0,0 +1,83 @@ +# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Video Dataset.""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import os + +from tensorflow.python.data.ops import dataset_ops +from tensorflow.python.data.util import nest +from tensorflow.python.framework import dtypes +from tensorflow.python.framework import ops +from tensorflow.python.framework import tensor_shape + +from tensorflow.python.framework import errors_impl +from tensorflow.python.framework import load_library +from tensorflow.python.platform import resource_loader +video_ops = None +for f in ['_video_ops_ffmpeg_3.4.so', '_video_ops_ffmpeg_2.8.so', '_video_ops_libav_9.20.so']: + try: + video_ops = load_library.load_op_library(resource_loader.get_path_to_datafile(f)) + break; + except errors_impl.NotFoundError as e: + print(e) + + +class VideoDataset(dataset_ops.DatasetSource): + """A Video File Dataset that reads the video file.""" + + def __init__(self, filenames): + """Create a `VideoDataset`. + + `VideoDataset` allows a user to read data from a video file with + ffmpeg. The output of VideoDataset is a sequence of (height, weight, 3) + tensor in rgb24 format. + + For example: + + ```python + dataset = VideoDataset("/foo/bar.mp4") + iterator = dataset.make_one_shot_iterator() + next_element = iterator.get_next() + while True: + try: + print(sess.run(next_element)) + except tf.errors.OutOfRangeError: + break + ``` + + Args: + filenames: A `tf.string` tensor containing one or more filenames. + """ + super(VideoDataset, self).__init__() + self._filenames = ops.convert_to_tensor( + filenames, dtype=dtypes.string, name="filenames") + + def _as_variant_tensor(self): + return video_ops.video_dataset(self._filenames) + + @property + def output_classes(self): + return ops.Tensor + + @property + def output_shapes(self): + return (tensor_shape.TensorShape([None, None, 3])) + + @property + def output_types(self): + return dtypes.uint8 diff --git a/third_party/ffmpeg_2_8.BUILD b/third_party/ffmpeg_2_8.BUILD new file mode 100644 index 000000000..9e5082774 --- /dev/null +++ b/third_party/ffmpeg_2_8.BUILD @@ -0,0 +1,64 @@ +# Description: +# FFmpeg + +licenses(["notice"]) # LGPL v2.1+ license + +exports_files(["LICENSE.md"]) + +load("@org_tensorflow//third_party:repo.bzl", "cc_import_library") + +cc_import_library( + name = "ffmpeg", + hdrs = [ + "libavformat/avformat.h", + "libavformat/avio.h", + "libavformat/version.h", + "libavcodec/version.h", + "libavcodec/avcodec.h", + "libavcodec/old_codec_ids.h", + "libavutil/avconfig.h", + "libavutil/samplefmt.h", + "libavutil/avutil.h", + "libavutil/common.h", + "libavutil/attributes.h", + "libavutil/macros.h", + "libavutil/version.h", + "libavutil/mem.h", + "libavutil/error.h", + "libavutil/rational.h", + "libavutil/mathematics.h", + "libavutil/intfloat.h", + "libavutil/log.h", + "libavutil/pixfmt.h", + "libavutil/old_pix_fmts.h", + "libavutil/buffer.h", + "libavutil/cpu.h", + "libavutil/channel_layout.h", + "libavutil/dict.h", + "libavutil/frame.h", + "libavutil/imgutils.h", + "libavutil/pixdesc.h", + "libswscale/swscale.h", + "libswscale/version.h", + ], + libraries = [ + "libavformat-ffmpeg.so.56", + "libavcodec-ffmpeg.so.56", + "libavutil-ffmpeg.so.54", + "libswscale-ffmpeg.so.3", + ], +) + +genrule( + name = "libavutil_avconfig_h", + outs = ["libavutil/avconfig.h"], + cmd = "\n".join([ + "cat <<'EOF' >$@", + "#ifndef AVUTIL_AVCONFIG_H", + "#define AVUTIL_AVCONFIG_H", + "#define AV_HAVE_BIGENDIAN 0", + "#define AV_HAVE_FAST_UNALIGNED 1", + "#endif /* AVUTIL_AVCONFIG_H */", + "EOF", + ]), +) diff --git a/third_party/ffmpeg_3_4.BUILD b/third_party/ffmpeg_3_4.BUILD new file mode 100644 index 000000000..d9bf26d70 --- /dev/null +++ b/third_party/ffmpeg_3_4.BUILD @@ -0,0 +1,62 @@ +# Description: +# FFmpeg + +licenses(["notice"]) # LGPL v2.1+ license + +exports_files(["LICENSE.md"]) + +load("@org_tensorflow//third_party:repo.bzl", "cc_import_library") + +cc_import_library( + name = "ffmpeg", + hdrs = [ + "libavformat/avformat.h", + "libavformat/avio.h", + "libavformat/version.h", + "libavcodec/version.h", + "libavcodec/avcodec.h", + "libavutil/avconfig.h", + "libavutil/samplefmt.h", + "libavutil/avutil.h", + "libavutil/common.h", + "libavutil/attributes.h", + "libavutil/macros.h", + "libavutil/version.h", + "libavutil/mem.h", + "libavutil/error.h", + "libavutil/rational.h", + "libavutil/mathematics.h", + "libavutil/intfloat.h", + "libavutil/log.h", + "libavutil/pixfmt.h", + "libavutil/buffer.h", + "libavutil/cpu.h", + "libavutil/channel_layout.h", + "libavutil/dict.h", + "libavutil/frame.h", + "libavutil/imgutils.h", + "libavutil/pixdesc.h", + "libswscale/swscale.h", + "libswscale/version.h", + ], + libraries = [ + "libavformat.so.57", + "libavcodec.so.57", + "libavutil.so.55", + "libswscale.so.4", + ], +) + +genrule( + name = "libavutil_avconfig_h", + outs = ["libavutil/avconfig.h"], + cmd = "\n".join([ + "cat <<'EOF' >$@", + "#ifndef AVUTIL_AVCONFIG_H", + "#define AVUTIL_AVCONFIG_H", + "#define AV_HAVE_BIGENDIAN 0", + "#define AV_HAVE_FAST_UNALIGNED 1", + "#endif /* AVUTIL_AVCONFIG_H */", + "EOF", + ]), +) diff --git a/third_party/libav_9_20.BUILD b/third_party/libav_9_20.BUILD new file mode 100644 index 000000000..09e864dc7 --- /dev/null +++ b/third_party/libav_9_20.BUILD @@ -0,0 +1,62 @@ +# Description: +# FFmpeg + +licenses(["notice"]) # LGPL v2.1+ license + +exports_files(["LICENSE.md"]) + +load("@org_tensorflow//third_party:repo.bzl", "cc_import_library") + +cc_import_library( + name = "libav", + hdrs = [ + "libavformat/avformat.h", + "libavformat/avio.h", + "libavformat/version.h", + "libavcodec/version.h", + "libavcodec/avcodec.h", + "libavcodec/old_codec_ids.h", + "libavutil/avconfig.h", + "libavutil/samplefmt.h", + "libavutil/avutil.h", + "libavutil/common.h", + "libavutil/attributes.h", + "libavutil/version.h", + "libavutil/mem.h", + "libavutil/error.h", + "libavutil/rational.h", + "libavutil/mathematics.h", + "libavutil/intfloat.h", + "libavutil/log.h", + "libavutil/pixfmt.h", + "libavutil/old_pix_fmts.h", + "libavutil/cpu.h", + "libavutil/channel_layout.h", + "libavutil/dict.h", + "libavutil/imgutils.h", + "libavutil/pixdesc.h", + "libavutil/time.h", + "libswscale/swscale.h", + "libswscale/version.h", + ], + libraries = [ + "libavformat.so.54", + "libavcodec.so.54", + "libavutil.so.52", + "libswscale.so.2", + ], +) + +genrule( + name = "libavutil_avconfig_h", + outs = ["libavutil/avconfig.h"], + cmd = "\n".join([ + "cat <<'EOF' >$@", + "#ifndef AVUTIL_AVCONFIG_H", + "#define AVUTIL_AVCONFIG_H", + "#define AV_HAVE_BIGENDIAN 0", + "#define AV_HAVE_FAST_UNALIGNED 1", + "#endif /* AVUTIL_AVCONFIG_H */", + "EOF", + ]), +) diff --git a/third_party/repo.bzl b/third_party/repo.bzl new file mode 100644 index 000000000..905d1bd8d --- /dev/null +++ b/third_party/repo.bzl @@ -0,0 +1,28 @@ +# cc_import_library is a replacement of cc_import in bazel, +# The purposes are: +# - Allows the specification of soname e.g., libavformat.so.57 +# - Restrict to only allow linking against for GPL licenses libraries, +# so that no srcs files are used. +def cc_import_library( + name, + hdrs, + libraries, + **kwargs): + for library in libraries: + native.genrule( + name = "stub-" + library, + outs = [library], + cmd = "echo '' | g++ -shared -fPIC -x c++ - -o $@", + ) + native.cc_library( + name = name, + srcs = [], + hdrs = hdrs, + copts = [], + defines = [], + includes = [], + linkopts = ["-L$(GENDIR)/external/" + native.repository_name()[1:]] + ["-l:" + x for x in libraries], + visibility = ["//visibility:public"], + data = libraries, + **kwargs + )