1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
// Copyright 2022-2023 Andrew D. Straw.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT
// or http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.

//! Associates an encoder with a writer to allow writing encoded frames.

use std::io::Write;

use super::{Error, LessEncoder, Result, YCbCrImage};

/// An encoding session ready to start but which has not yet necessarily encoded
/// its first frame.
///
/// This mainly exists to hold the writer but defer writing until we have the
/// first frame (in the `Configured` variant). After the first frame is written,
/// it will be in the `Recording` variant. (The `MovedOut` variant should never
/// be observed and represents a temporary internal state.)
enum WriteState<W> {
    Configured(W),
    Recording(RecordingState<W>),
    MovedOut,
}

impl<W: Write> WriteState<W> {
    fn write_frame(&mut self, frame: &YCbCrImage) -> Result<()> {
        // Temporarily replace ourself with a dummy value.
        let orig_state = std::mem::replace(self, WriteState::MovedOut);
        let state = match orig_state {
            WriteState::Configured(fd) => {
                let (initial_nal_data, encoder) = LessEncoder::new(frame)?;
                let mut state = RecordingState { wtr: fd, encoder };
                state
                    .wtr
                    .write_all(&initial_nal_data.sps.to_annex_b_data())?;
                state
                    .wtr
                    .write_all(&initial_nal_data.pps.to_annex_b_data())?;
                state
                    .wtr
                    .write_all(&initial_nal_data.frame.to_annex_b_data())?;
                state
            }
            WriteState::Recording(mut state) => {
                let encoded = state.encoder.encode(frame)?;
                state.wtr.write_all(&encoded.to_annex_b_data())?;
                state
            }
            WriteState::MovedOut => {
                return Err(Error::InconsistentState {
                    #[cfg(feature = "backtrace")]
                    backtrace: std::backtrace::Backtrace::capture(),
                })
            }
        };

        // Restore ourself to the correct state.
        *self = WriteState::Recording(state);

        Ok(())
    }
}

/// Small helper struct holding writer and encoder for an ongoing encoding
/// session.
struct RecordingState<W> {
    wtr: W,
    encoder: LessEncoder,
}

/// Write images to an [std::io::Write] implementation in `.h264` file format.
pub struct H264Writer<W> {
    inner: WriteState<W>,
}

impl<W: Write> H264Writer<W> {
    /// Create a new [H264Writer] from an [std::io::Write] implementation.
    pub fn new(wtr: W) -> Result<Self> {
        Ok(Self {
            inner: WriteState::Configured(wtr),
        })
    }

    /// Retrieve the underlying [std::io::Write] implementation.
    pub fn into_inner(self) -> W {
        match self.inner {
            WriteState::Configured(w) => w,
            WriteState::Recording(state) => state.wtr,
            WriteState::MovedOut => {
                unreachable!("inconsistent internal state");
            }
        }
    }

    /// Encode and write a frame
    pub fn write(&mut self, frame: &YCbCrImage) -> Result<()> {
        self.inner.write_frame(frame)
    }
}