Use PyAV fork and set hvc1 codec tag for H.265 (#58309)

This commit is contained in:
uvjustin 2021-10-27 17:04:46 +08:00 committed by GitHub
parent f6e38fc4e2
commit 35acca1063
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 60 additions and 10 deletions

View File

@ -2,7 +2,7 @@
"domain": "stream",
"name": "Stream",
"documentation": "https://www.home-assistant.io/integrations/stream",
"requirements": ["av==8.0.3"],
"requirements": ["ha-av==8.0.4-rc.1"],
"dependencies": ["http"],
"codeowners": ["@hunterjm", "@uvjustin", "@allenporter"],
"quality_scale": "internal",

View File

@ -140,6 +140,8 @@ class SegmentBuffer:
self._output_video_stream = self._av_output.add_stream(
template=self._input_video_stream
)
if self._output_video_stream.name == "hevc":
self._output_video_stream.codec_tag = "hvc1"
# Check if audio is requested
self._output_audio_stream = None
if self._input_audio_stream and self._input_audio_stream.name in AUDIO_CODECS:

View File

@ -347,9 +347,6 @@ auroranoaa==0.0.2
# homeassistant.components.aurora_abb_powerone
aurorapy==0.2.6
# homeassistant.components.stream
av==8.0.3
# homeassistant.components.avea
# avea==1.5.1
@ -767,6 +764,9 @@ gstreamer-player==1.1.2
# homeassistant.components.profiler
guppy3==3.1.0
# homeassistant.components.stream
ha-av==8.0.4-rc.1
# homeassistant.components.ffmpeg
ha-ffmpeg==3.0.2

View File

@ -238,9 +238,6 @@ auroranoaa==0.0.2
# homeassistant.components.aurora_abb_powerone
aurorapy==0.2.6
# homeassistant.components.stream
av==8.0.3
# homeassistant.components.axis
axis==44
@ -466,6 +463,9 @@ growattServer==1.1.0
# homeassistant.components.profiler
guppy3==3.1.0
# homeassistant.components.stream
ha-av==8.0.4-rc.1
# homeassistant.components.ffmpeg
ha-ffmpeg==3.0.2

View File

@ -37,7 +37,7 @@ def generate_audio_frame(pcm_mulaw=False):
return audio_frame
def generate_h264_video(container_format="mp4", duration=5):
def generate_video(encoder, container_format, duration):
"""
Generate a test video.
@ -51,7 +51,7 @@ def generate_h264_video(container_format="mp4", duration=5):
output.name = "test.mov" if container_format == "mov" else "test.mp4"
container = av.open(output, mode="w", format=container_format)
stream = container.add_stream("libx264", rate=fps)
stream = container.add_stream(encoder, rate=fps)
stream.width = 480
stream.height = 320
stream.pix_fmt = "yuv420p"
@ -82,6 +82,16 @@ def generate_h264_video(container_format="mp4", duration=5):
return output
def generate_h264_video(container_format="mp4", duration=5):
"""Generate a test video with libx264."""
return generate_video("libx264", container_format, duration)
def generate_h265_video(container_format="mp4", duration=5):
"""Generate a test video with libx265."""
return generate_video("libx265", container_format, duration)
def remux_with_audio(source, container_format, audio_codec):
"""Remux an existing source with new audio."""
av_source = av.open(source, mode="r")

View File

@ -40,7 +40,7 @@ from homeassistant.components.stream.core import StreamSettings
from homeassistant.components.stream.worker import SegmentBuffer, stream_worker
from homeassistant.setup import async_setup_component
from tests.components.stream.common import generate_h264_video
from tests.components.stream.common import generate_h264_video, generate_h265_video
from tests.components.stream.test_ll_hls import TEST_PART_DURATION
STREAM_SOURCE = "some-stream-source"
@ -201,6 +201,9 @@ class FakePyAvBuffer:
def __str__(self) -> str:
return f"FakeAvOutputStream<{template.name}>"
def name(self) -> str:
return "avc1"
if template.name == AUDIO_STREAM_FORMAT:
return FakeAvOutputStream(self.audio_packets)
return FakeAvOutputStream(self.video_packets)
@ -771,3 +774,38 @@ async def test_has_keyframe(hass, record_worker_sync):
await record_worker_sync.join()
stream.stop()
async def test_h265_video_is_hvc1(hass, record_worker_sync):
"""Test that a h265 video gets muxed as hvc1."""
await async_setup_component(
hass,
"stream",
{
"stream": {
CONF_LL_HLS: True,
CONF_SEGMENT_DURATION: SEGMENT_DURATION,
CONF_PART_DURATION: TEST_PART_DURATION,
}
},
)
source = generate_h265_video()
stream = create_stream(hass, source, {})
# use record_worker_sync to grab output segments
with patch.object(hass.config, "is_allowed_path", return_value=True):
await stream.async_record("/example/path")
complete_segments = list(await record_worker_sync.get_segments())[:-1]
assert len(complete_segments) >= 1
segment = complete_segments[0]
part = segment.parts[0]
av_part = av.open(io.BytesIO(segment.init + part.data))
assert av_part.streams.video[0].codec_tag == "hvc1"
av_part.close()
await record_worker_sync.join()
stream.stop()